Navigácia
ELOWEN · Dokumentácia

Posledná aktualizácia: 20. októbra 2026

Prechádzať dokumentáciu · Isolated fixtures and CLI evidence

Tento článok zatiaľ nie je preložený. Je dostupný v angličtine.

Developer reference

Isolated fixtures and CLI evidence

Released-client upgrade artifact preflight

scripts/tests/d5-client-upgrade-e2e/prepare.mjs prepares local artifact evidence for the released Linux/x64 0.30.6 client and an isolated 0.30.7 candidate. It never builds, extracts, installs, starts a daemon/client, accesses a publisher, or signs anything.

Its required inputs are a clean frozen source checkout and full commit, a separately authorized copied native build below the temporary directory (not a worktree), the candidate client archive and publisher-signed descriptor, the original released archive, and a new output file. Run node scripts/tests/d5-client-upgrade-e2e/prepare.mjs with --source, --head, --candidate, --archive, --descriptor, --old-archive and --output. There is no trust override or signing-key input.

Preflight uses the copied candidate's native verifyClientRelease and verifyFileBytes without a key override. A new test signing key therefore fails the same release verification required by the untouched old updater. Daemon test licence injection does not change client-release trust. The old archive has a fixed byte pin from local release-store inspection; its publisher descriptor signature is unavailable and is explicitly not claimed. Neither artifact is rebuilt or patched by preflight. The source commit is rechecked before and after verification and the complete compiled-tree digest must remain unchanged. The receipt records status: prepared and runtimeAcceptance: not-run; it does not establish that the selected build was produced from that source or that any runtime scenario passed.

The runtime scenario in run.mjs is implemented but is not runtime acceptance until it has actually run against an explicitly frozen and authorized candidate. It requires: the genuine old client bootstraps its own installed prefix and pairs through the native device-code owner; it is offline during the candidate cut; the unchanged process receives HTTP 409 and still commits the signed 0.30.7 package; a documented one-time restart launches the new client from that same prefix; the new strict ready/policy contract, a real command and a fresh 0.30.7 pairing all succeed. The intended owners are native build-client.mjs, daemonHarness.mjs ephemeral PostgreSQL, the test-only licence/daemon entry, and production pairing/admission, updater and executor code. No replacement installer, handshake or decoder is used. The daemon may run only from the copied built candidate inside the approved bwrap boundary with production roots and privileged sockets hidden, never from a worktree. Version 0.30.7 is the authorized synthetic test-candidate identity, staged only in a new scratch copy; receipts label it synthetic-test-candidate, not a published release. Source package/lock files remain untouched. A trusted signature for the exact new client bytes remains mandatory for the released-updater proof; it is independent of fixture metadata. No release preparation, publishing or signing happens here.

After explicit freeze and actual-run authorization, use the same preparation inputs plus --old-core-archive for the pinned original 0.30.6 core archive:

heavy-gate node scripts/tests/d5-client-upgrade-e2e/run.mjs --source <frozen-checkout> --head <full-commit> --candidate <copied-built-tree> --archive <0.30.7-linux-x64.tgz> --descriptor <publisher-signed.json> --old-archive <released-0.30.6-linux-x64.tgz> --old-core-archive <released-core-0.30.6.tgz> --output <new-evidence.json>

The copied built tree must include its native scripts, compiled database testing package and available existing dependencies. The runner copies it again, binds bare workspace imports to that copy's own database/shared modules, and asserts those module identities before boot. It never installs dependencies. Release archives are inspected and extracted as data by a separate python3 -I script into new empty directories with the standard tar data filter. Original byte pins are rechecked; client bundles and trust are never patched.

The runner launches a new network/PID namespace and hides production instance, privileged socket, install and nginx state. Only owned temporary state and the new private raw-log directory are writable. Both daemons run from copied release/build trees, on one loopback port over the same ephemeral PostgreSQL cluster. The full cutover passes the released core's shipped migration directory to startHarnessPostgres(migrationsDirectory): the first daemon must receive its own schema epoch, not a database already migrated by the candidate. The candidate then performs its ordinary forward migrations at boot; no ledger is rewritten. Omitting the directory retains the selected build's native default for fresh-only and other same-version harnesses. Native device-code pairing and first-run bootstrap install the real extracted client. A transparent gate cuts its network during the 0.30.6-to-candidate daemon handoff; its standard HTTP/WebSocket parsers only observe unchanged native responses. Policy controls are validated by the candidate's shared strict schema. HTTP 409 is produced by native admission, never by the gate. The updater's original PID must remain alive until its native journal says committed, with the installed version 0.30.7 and the executor still disconnected.

The one-time rollout restart stops only that owned client, then launches the same prefix's stable bin/elowen connect. Admission observations are correlated by the native request's target identity. Strict ready must match that target's strict policy-bearing ticket, actual policy cache, and the native client's complete Connected to Elowen. line, emitted only after its admission promise resolves. Cache adoption is checked again after an authenticated configuration change emits retry-policy for the same target. Command execution uses the real Terminal API/socket through Sandbox and cliConnections.control.openShell; its exit, output and client-owned effect file must agree. A separate home/prefix then pairs, bootstraps, connects and executes from the actual 0.30.7 archive. No synthetic executor or success-shaped handshake is used. The old updater's real cadence is unchanged; a failed check can require five minutes, so committed-package observation has an eight-minute deadline.

The evidence file is exclusive-created and raw logs remain beside it in <output>.logs (private directory; pairing-code stdout is discarded). It claims runtime success only after every ordered milestone, clean owned-process/PG teardown and unchanged final artifact/source identities. Missing signatures fail before any extraction or process startup; skipped, failed and cleanup-failed runs cannot report acceptance. Neither signing nor candidate fixture version staging is performed here.

Focused tests use native source release parsers/verifiers and pure-helper rejection inputs only, without a daemon, database or client process:

node --import ./scripts/registerTsResolve.mjs --experimental-transform-types --disable-warning=ExperimentalWarning --test scripts/tests/d5-client-upgrade-e2e/*.test.mjs

Fresh pairing without a release-feed descriptor

scripts/tests/d5-client-upgrade-e2e/pairing.mjs is a separate, explicitly fresh-only actual phase. After joint freeze and actual-run authorization:

heavy-gate node scripts/tests/d5-client-upgrade-e2e/pairing.mjs --source <frozen-checkout> --head <full-commit> --candidate <copied-built-tree> --archive <locally-built-0.30.7-linux-x64.tgz> --output <new-fresh-evidence.json>

It needs no released archive or client-release descriptor. The local candidate archive is byte-pinned for reproducibility, not authenticated as a release. The receipt names the synthetic identity and descriptorSignature: not-supplied-or-authenticated. The real extracted CLI performs its own native device-code login, approval, credential/policy persistence and first-run standalone bootstrap. No credentials, prefix metadata, installer, admission reply or executor are fabricated. This proves native pairing/install-from-local-package behavior, not customer installer authentication or the old client's upgrade.

The separate phase uses the same isolation.mjs boundary and copied-runtime bindings as the full runner. Both consume nativeClients.mjs, the sole test-fixture owner for native pairing/bootstrap, strict-ready acknowledgement and the real Terminal-to-Sandbox command. Fresh pairing additionally checks the native enrollment policy cache before connecting, then observes authenticated strict admission and a real command's exit/output/effect file. No release publisher is started or descriptor supplied; independent background updater feed checks can fail natively against the isolated unavailable licence-server address. Client-release trust and update cadence remain unchanged.

Its exclusive receipt is always scope: fresh-pairing-only and fullUpgradeAcceptance: not-run; only pairingAcceptance can pass after all three fresh milestones and clean teardown. The mandatory full runner has no skip option and still refuses a missing or untrusted signed descriptor before starting anything. A fresh-only pass cannot satisfy or relabel that blocked full proof. Neither entry builds a candidate or authorizes its own execution.

Isolated daemon test preparation

The API, brain, migration, licence and web E2E harnesses share test-only preparation in scripts/tests/lib/daemonHarness.mjs: call isolatedChildEnv(dataDir, scenarioValues), allocate freePort(), then await waitForHealth(baseUrl, timeoutMs, readinessPredicate). Inherited production ELOWEN_* and agent configuration paths are stripped; HOME, DB, project state and logs point into the temporary scenario directory. Explicit scenario values remain caller-owned. The sole inherited Elowen key is ELOWEN_E2E_DIST, made absolute before children change working directory. resolveTestEntry.mjs checks the selected build's daemon/main.js for test-licence boot, or daemon/index.js for the licence scenario's real locked boot. Without a selector it uses the checkout's own dist/; a missing selected entry throws before spawn without switching builds. Brain also imports its restart exit code from that same selected build.

brain-e2e/spawn-daemon.mjs additionally accepts optional synchronous prepareDataDir(dataDir), called once before PostgreSQL, test-licence minting and first daemon boot. It writes fixtures only below that isolated instance directory; without it, preparation is unchanged. The registry's tests/e2e/harness/spawn-daemon.mjs uses this seam to install plugins and collect their licence entitlements, delegating to the exact native lifecycle through existing coreContract/register-loader. It retains the same database observation handle, filtered child environment, restart and teardown owners. Selection is explicit per-call env.ELOWEN_E2E_DIST, then the process selector, then the selected core's dist; a missing selected entry fails before native spawn, never by falling back to another build. Selecting dist or TypeScript paths alone does not retarget bare runtime packages: verify candidate bytes and one module/class identity for bare and relative imports inside the isolated runtime before daemon acceptance. No dependency installation, new loader or host dependency mutation is part of this preparation.

Minimal use, as brain-e2e/spawn-daemon.mjs does it:

const port = await freePort();
const baseUrl = `http://127.0.0.1:${port}`;
const env = isolatedChildEnv(dataDir, { ELOWEN_PORT: String(port) });
const entry = resolveTestDaemonEntry('main.js', env); // throws before spawn if the selected build is missing
// Spawn `entry` with `env`, then wait for readiness:
await waitForHealth(baseUrl, 30_000, (body) => body?.ok === true && body.platformsReady === true);

Ports are released ephemeral IPv4 loopback ports, not reserved leases. Readiness polls every 100 ms and bounds requests and response-body reads by the total deadline. Brain requires platformsReady: true; the locked licence scenario accepts ok: true alone. A readiness timeout reports the last failure. Spawn, licence minting, restart, logs and teardown remain scenario-local. These helpers never ship or run on normal boot. Preparation regression tests mock child processes and build artifacts, so they do not execute a daemon or touch live state; full E2E proof still requires a separately built, isolated test environment. The preparation behavior is tested in tests/scripts/daemonHarness.test.ts, and the entry selection rules have their own test in tests/scripts/resolveTestEntry.test.ts. The transport helpers in scripts/tests/lib/sseStream.mjs and scriptedOpenAi.mjs are also test-only. openSseStream(baseUrl, path, token, observers) opens authenticated HTTP and incrementally decodes UTF-8 and LF-separated SSE frames. Scenario adapters own their counters, readiness promises, predicates and timeout messages: for example, delegate-e2e/run.mjs awaits openEventCollector's connected promise, which the collector resolves from the : connected subscription comment, while hooks reads the idle counter openSessionStream keeps through onEvent. The parity and lazy-MCP watchers use onLine to retain raw-line idle counting and their historical lack of an HTTP status assertion. Readers discard incomplete/non-JSON frames and tolerate transport teardown as before; abort() cancels immediately and close(deadlineMs) bounds reader teardown for the API lifecycle assertion. Memory follows the current incomplete frame or line, while scenario event collections remain scenario-owned.

Minimal stream use, as continuity-e2e/run.mjs does it:

const transport = await openSseStream(baseUrl, `/brain/stream?session=${encodeURIComponent(session)}`, token, {
  onEvent(evt) { if (evt.type === 'idle') idles += 1; },
});
// ...when the scenario is finished:
transport.abort();

Scripted model servers import readJson, chunkFrame, contentText, startCompletionStream and endCompletionStream directly. Empty or malformed request bodies record null; valid JSON scalars are retained. Content matching preserves spaces for non-text parts, and response headers, data bytes and the terminal [DONE] marker are shared. Scenario modes, chunk ids, usage, tool calls, held-response timing and server teardown remain local, for example in recovery-e2e/model.mjs. These helpers only run when a test explicitly opens a stream or starts a fixture; they add no daemon startup work or external services. Continuity waits for both native idle and the host's non-streaming turn control, so automatic compaction finishes before the next send. Its prompt probe selects the first chat completion after admission, skipping independent model-catalog requests and excluding later summary requests; it still reads only that request's final user message. The helper contracts are tested in tests/scripts/e2eTransport.test.ts, and the probe in tests/scripts/continuityPrompt.test.ts. Run the continuity suite with npm run test:e2e:continuity. Each turn gets 40 seconds to settle, and the suite adds at most 14 filler turns while it waits for a compaction (scripts/tests/continuity-e2e/run.mjs:108 and :145).

Recovery E2E keeps scripts/tests/recovery-e2e/run.mjs as the sequential package and CI entrypoint. It runs the registered numbered scenarios under recovery-e2e/scenarios/ by default; RECOVERY_E2E_ONLY=1,7 selects explicit scenario numbers. Each scenario owns its scripted model, real daemon, restart sequence and finally teardown. The test-only recovery-e2e/lib.mjs owns shared read-only DB observations, the store's display projection, provider-wire checks, silent-resume assertions and the attached client's real transcript fold. setCurrentDaemon(daemon) updates the timeout log source as soon as a scenario boots. Failure counts and restart timing evidence are shared with the runner; polling deadlines, log tails and the three-second pause bound are unchanged. The recovery, subagent and parity suites assert the forked-runner path unconditionally and fail when no fork served the delegated turn. The scripts no longer read ELOWEN_SUBAGENT_RUNNER. Daemon preparation continues through spawnRealDaemon, including its selected scratch build; these modules add no production entrypoints, persistence or background work. Run it with npm run test:e2e:recovery. CI runs the same entrypoint in the recovery-e2e job (.github/workflows/ci.yml:268). Polling waits default to a 60 second deadline (scripts/tests/recovery-e2e/lib.mjs:13), and the pause check allows 3 seconds for a clean exit (lib.mjs:16).

Paired-computer recovery preparation

recovery-e2e/paired-executor.mjs takes the isolated spawnRealDaemon handle, not a database URL. It enrolls through native users.apiTokens.createCliDevice in a serializable transaction, obtains POST /cli/connections/ticket, and uses one-shot /ws/cli/executor admission. It reuses shared createLocalExecutor and the selected candidate's process watchdog. Files remain below the scenario root; no physical computer, production database or service belongs to this fixture.

The sequential recovery runner prepares the following additional cases:

Selector Prepared scenario
13,14,15,16 Owner live/boot mutation loss and live/boot model-only loss
17,18,19,20 The same four variants for a delegated call
21,22,23,24 The same four variants for a workflow node
25 Owner Stop while offline, followed by boot/reconnect
26,27 Queued/committed child-result receipts while the parent computer is offline
28 Native MCP HTTP discovery/boot with unknown SDK response-stream progress
29 Authenticated policy API and native CLI-chat policy/SSE parity

For example, RECOVERY_E2E_ONLY=13,14,15,16 selects the owner variants against the isolated built candidate selected by ELOWEN_E2E_DIST; selectors are comma-separated numbers, not ranges. Case 13's first two attempts FAILED (exit 1 each). The first failed when a raw JSONB execution-target observation bypassed the repository's serialized authority row. Awaited/asserted source ordering places that failure after the mutation, lost final ACK, one effect and durable computer wait; those values were not separately serialized. Budget, resume, settlement and final journal ACK/Read checks were NOT REACHED; native finally cleanup followed. That first failure is a fixture observation defect, not an established product recovery defect.

In the second attempt, the canonical authority assertion passed. Four JSON milestones retained session/Project/target and prerequisite identities, one effect with its dropped final process-exit ACK, a conversation prerequisite waiting at generation 1, and the before-budget worker-request count 1, park count 0, delivery rows 0 and attempt total 0. After two authenticated replacement sockets, the required COMPUTER_DONE journal-answer wait timed out at 60 seconds. Logs report accepted-turn connection loss followed by an already-ended/nothing-to-continue park message; the timeout's root cause and product/fixture classification were UNKNOWN in that retained run. Final completion, prerequisite settlement, Read/mutation verification and completed-milestone checks were NOT REACHED; there is no final effect-count, no-replay or exactly-once journal ACK proof. Live case 13 has no restart, so offline-after budget comparison is not applicable. Native finally cleanup and absence of remaining owned processes/namespace holders were verified; this does not change the FAILED outcome. Cases 14–29 remain PREPARED, NOT EXECUTED. Neither of these historical attempts establishes passing recovery acceptance.

Separately, tests/brain/localExecutionRecovery.test.ts reproduced a native product integration defect: after a committed computer wait, accepted-send failure publication appended a terminal chat event, causing the continuation tail classifier to return nothing without a resumed provider request. Publication variants with one or two replacement sockets failed; their non-publication controls passed, with original completion joined before reconnect. The proven-wait outcome now keeps that publication nonterminal. Regression coverage includes delayed publication after resume, native Stop, exactly-once API settlement, raw terminal errors and refused/failed/stale/revoked park proofs. This is source-unit causal evidence, not the historical E2E's unretained raw journal/model tail. Both earlier actual case-13 attempts remain FAILED.

A later rebuilt-candidate attempt 1 also FAILED (exit 1): it emitted prerequisite settlement, then failed the old unknown-outcome prose substring assertion. The actual wire/native result body was not retained before cleanup, so its body and product/fixture classification remain UNKNOWN. The native-contract checks below do not reconstruct that historical body or relabel the failure.

On 19 October 2026, the final rebuilt candidate's attempt 2 PASSED actual LIVE owner mutation/reconnect case 13 (RECOVERY_E2E_ONLY=13), with runner process exit 0 after native finally/cleanup. All seven structured milestones were retained: session start, dropped mutation ACK, computer wait, waiting budget, prerequisite settlement, mutation verification and completion. The final counts were one effect, one mutation dispatch, one verification Read and one wire result. The prerequisite retained its exact id/generation as it settled; the original local Project target was retained and cancelled stayed false.

The native checks passed for exactly one original Bash journal result, isError: true, exact projected wire/native text, the applicable producer contract, wire ordering and model/executor health. The successful run did not serialize which accepted uncertainty-text producer supplied that result; it does not establish a specific producer branch or recover attempt 1's body. The private failure capture was not triggered, so actual failure-file I/O was not exercised by this success.

The fresh canonical package-binding proof matched the candidate; the complete 2,498-file compiled artifact stayed byte-identical before the run and after cleanup, with no remaining owned native process or namespace holder. This acceptance covers only LIVE owner case 13, not restart/boot, delegated/child-inbox recovery, cases 14–29 or any of the 16 prepared platform cases. The generic runner PASS banner and restart-timings heading do not prove those paths executed. Focused source-fixture passes below remain separate evidence.

Mutation cases flush one operation-ID append and drop only its final process acknowledgement. The scripted model answers the original tool-call ID and verifies with Read, never by repeating the mutation. Assertions retain the original target, call/node/session, durable prerequisite generation and parent inbox/journal ACK. Two independently authenticated replacement sockets race native wake paths; offline boot must spend no model, origin-recovery or delivery budget. Model-only and Stop cases introduce no mutation. Each scenario owns executor/model teardown.

Initial/final target assertions and platform session selection read native transcript.getProjectExecution(sessionId) authority rows inside read-only repeatable-read transactions, preserving the native parser and one-generation invariant. Raw session observations select only counters or candidate IDs, never feed a SELECT * JSONB object into the serialized authority parser.

Both paired and platform mutation checks require exactly one native journal toolResult matching the original toolCallId and toolName: 'Bash', with isError: true, plus one wire answer equal to native extractText(result). The compiled isNonUserSession classifier selects the native session context, not the scenario's origin label. Platform channels remain non-user sessions even on a local Project. The accepted producer contracts are:

  • Live dispatched connection loss, in either context: The local connection was lost after dispatch; the mutation outcome is unknown. Do not replay it.
  • Synthetic local owner-chat interruption: [interrupted] This Bash call was interrupted by a daemon restart; its mutation outcome on the computer is unknown. Verify the computer's current state before repeating it. It requires details.interrupted: true and details.mutationOutcome: 'unknown'.
  • Synthetic non-user interruption, including delegated and platform sessions: [interrupted] This Bash call was interrupted by a daemon restart; it may or may not have taken effect. Verify the current state before repeating it. It requires details.interrupted: true.

Text equality and producer-contract checks assert boolean comparisons rather than printing value diffs, so unredacted wire/native text cannot enter assertion stacks.

The paired and platform fixtures emit bounded JSON stdout at reached milestones only, never from polling iterations. Records identify native session/child/tool/ workflow, Project/target and prerequisite identity/generation/state where applicable, plus observed effect counts and dropped ACK ID/type/result-kind/final. Before/offline budget records retain park/origin/model counts and exact delivery-row/attempt totals; delivery ID/attempt samples are capped at eight rows, not a complete anomalous list. Full arrays remain in assertions. At most nine milestone records appear per scenario. Mutation verification, inbox ACK, cancellation, settlement and completion appear only if reached; a milestone is an observation, not whole-scenario acceptance. Platform records retain envelope presence, never its payload. No configuration, credentials, commands, provider/transcript/HTTP/executor-result payloads or external artifacts are written by this evidence seam. Completion precedes unchanged native finally teardown and is not cleanup-success evidence; executor, daemon/pool/cluster and model owners still dispose their resources.

Separately, a paired-scenario native-result check failure retains bounded private evidence in .artifacts/recovery-e2e-*/unknown-outcome.jsonl, outside scenario cleanup and compiled-artifact hash roots. The platform helper does not write this file. A unique directory and exclusive wx creation use mode 0600; both the first write and optional append flush before native teardown.

At most two JSONL records retain the wire observation first, then at most one cached native result plus its match count, error flag and interruption markers. Samples use native redactProblemText and stay within 500 characters; each has originalBytes, originalSha256 and sampleChanged, not the complete raw body. Native observation is never reread for capture. If that read fails, the wire-only record survives and the original error remains visible. A capture-write failure throws AggregateError containing both the original and evidence errors rather than masking either. Retained failure evidence is not a passing scenario or cleanup-success proof.

Cases 26/27 extend resultInboxRestart.mjs and result-restart-model.mjs, not a new receipt mechanism. They disconnect only after the child result already exists, compare the original inbox identity/attempts and settledResultIds across offline restart, then require one journal delivery after duplicate reconnect hints without rerunning the child. The read-only snapshot observes journal and inbox together.

Case 28 uses the real MCP SDK against a loopback HTTP wire fixture through the public instance-server API: initialize/tools/list discovery and supervised boot retain sdkResponseStream counters/deadlines as null and readiness as unknown. It prepares visibility/discovery checks, not SDK response-resume timing or hidden internal retry-counter proof. Case 29 uses the selected compiled BrainClient with real authenticated /connection-policy and instanceEvents: default delays are 2/4/8 seconds without jitter; a revision-CAS update to maxRetries: 0 and baseDelayMs: 1500 must arrive through retry-policy SSE without reopening the healthy episode. This is CLI-chat/API parity, not paired-client retry-loop proof.

The shared test-only recovery-e2e/platform-computer.mjs exports scenarioPlatformComputerRecovery({ daemon, token, platform, command, send, texts, restart, modelOnly }). Existing registry Discord gateway/slash, Telegram polling, WhatsApp bridge and Teams signed-webhook callers each prepare live/boot crossed with mutation/model-only loss: 16 cases total. Transport fixtures deliver /new, a setup turn, /project and the task through actual adapters; native readers locate the original session/Project and durable platform envelope. Assertions cover the same authenticated executor/lost ACK, stable offline budgets/envelope, two reconnect sockets, one answer to the original room and settled journal/delivery. No direct brain send, forged envelope, private wake or second database owner replaces that path. All 16 cases are PREPARED, NOT EXECUTED; they prove no live-platform acceptance.

The focused provider check is npx vitest run --config scripts/tests/recovery-e2e/vitest.config.mjs scripts/tests/recovery-e2e/computer-model.test.mjs --maxWorkers=2 inside the required isolation boundary. Knip's root Vitest plugin explicitly loads this focused configuration alongside vitest.config.ts, so its default export and source-fixture entries are analyzed as test configuration, not daemon scripts. Its nine passing cases cover the scripted provider, actual local append commands and a task following an earlier platform setup turn, not authenticated real-daemon recovery. computer-recovery.test.mjs has 18 source-fixture checks through the actual paired/platform scenario source and compiled native authority/session/text helpers, trapping daemon, executor and transport boundaries. Named JSDoc types bind the VM-loaded scenario calls to the actual exported function signatures; this also makes these real source-fixture consumers visible to static analysis. They cover live producer text in both callers, context-correct synthetic acceptance, wrong-context or non-error/text-mismatch rejection, bounded failure capture and error/cleanup preservation. Their 18 passing cases are source-fixture evidence, not actual daemon, client or platform acceptance; all 16 native-adapter cases remain unexecuted.

Registry source fixtures also retain the candidate's actual ConnectionStatus through the existing Vitest alias and tests/ui/hostRuntime.tsx. Teams turn parity supplies native retry context and awaits disconnect; its ShareFile provider swap uses native connections/routing credentials. harness-source.test.ts checks missing-dist rejection and exact native lifecycle/preparation/licence delegation without spawning a daemon. finalDeliveryFailure.test.ts uses the selected core's actual logger, sink and console emission while retaining exactly one cs/sk/en notice and HTTP 503 diagnostics. platformTurnParity.test.ts keeps exact code/reporting metadata and Discord's original failure identity, including toBe(failure). These focused fixture checks and a successful source build do not establish passing daemon/platform E2E, browser or physical Linux/Windows acceptance.

CLI tmux driver and evidence ownership

The CLI tmux scenarios share test-only process and input primitives in scripts/tests/lib/tmuxDriver.mjs. Bind createTmuxInput(server, session) to the private server returned by createTmuxServer(label); use shellQuote for launch arguments and startMock(fixture, spawnOptions) for the fixture's child and output snapshots. The private tmux server inherits only PATH, HOME, LANG, LC_ALL and TMPDIR when present. This fake-daemon harness models a human terminal: ambient agent-shell access markers, daemon credentials and URLs never enter the server. Each scenario supplies its isolated CLI home, fake daemon URL and fake token explicitly; cli-tmux-goal.mjs is one consumer. This does not change the production CLI's agent-shell guard. Creating a server snapshots the five permitted environment variables once, with no extra process or network request. Readiness parsing, fixture environment, teardown and scenario assertions remain caller-owned. Deadline polling keeps each scenario's existing interval, strict or retrying predicate errors and timeout capture. Rail polling separately preserves its 200 attempts and final 50 ms sleep. No helper starts a daemon or builds code. The default deadline interval is 30 ms. The driver's tests, including a real pane probe for inherited agent access and credentials, are in scripts/tests/lib/tmuxDriver.test.mjs.

Evidence has six direct owners under scripts/tests/tmux-evidence/: artifacts creates fresh scenario directories, cleans process-owned temporary directories and writes reports; frames reads strict or live JSONL and validates diagnosed frames; capture binds plain/ANSI/state triplets within one completed frame and owns pane analysis; diagnostics summarizes timing and viewport work; metadata binds the Git commit, complete dist hash and run identity; aggregate revalidates all raw evidence against those owners. Capture retries remain capped at six, ordinary frames remain below 50 ms, and required labels and report fields are unchanged. Scrollbar visibility and drag coordinates use one isolated-cell finder restricted to transcript rows. That finder is scrollbarThumb in tmux-evidence/capture.mjs:121, and the drag in cli-tmux.mjs:553 takes its coordinates from it.

For example, cli-tmux.mjs imports capture analysis and the input driver directly; cli-tmux-built.mjs runs support, driver and native fake-daemon contract tests before its four evidence scenarios and validates the completed root through aggregateTmuxReports. The fake daemon serves authenticated GET /connection-policy with Cache-Control: no-store and sends the same fixed policy as the initial retry-policy event on /events. The built BrainClient requires that policy before opening either stream; a missing route fails connection bootstrap rather than selecting defaults. Goal timestamps use the repository's ISO 8601 UTC wire format, not zone-less database text. cli-tmux-brain.test.mjs exercises both native streams, policy authentication and goal timestamp serialization without tmux. Missing, stale, malformed or inconsistent evidence still fails the gate. The fake daemon closes foreground Delegate calls with tool_end when POST /brain/subagents/background detaches them, while their child sessions remain running. This matches the production detach contract and lets the long scenario assert zero idle animation timers after returning to the parent. These modules run only in the test harness. Support tests use independent expected capture labels, analysis, performance and build-identity fixtures, not the analyzers' own output as their oracle. Full CLI proof requires a separately built test checkout with tmux. CI runs the gate with npm run test:cli-tmux:built (.github/workflows/ci.yml:134). When ELOWEN_TMUX_RUN_ID is unset, the gate generates a local- run ID once and passes it to every scenario. The gate also refuses a configured ELOWEN_TMUX_ARTIFACT_ROOT that already holds files (scripts/tests/cli-tmux-built.mjs:13-21).

Brain service scenario fakes share the tool loadout through fakePiToolState() in tests/helpers/fakePiSession.ts. Spread its result into the fake session; the existing createSession callback fills __tools, __active and model for each spawn. Each call creates independent empty tool and active-name arrays, an empty rendered system prompt, an unset model slot and a tool-selection spy. It acts only in tests that explicitly include it and installs no runtime hooks. Without it, a scenario supplies its own tool surface. It does not model prompts, events, persistence, queues, thinking, compaction or billing. The shared BrainService fixture in tests/brain/brainService/fixtures.ts, plus idleLiveSessionLifecycle.test.ts, multiClientContract.test.ts and sessionTeardown.test.ts, are current consumers of that fixture rather than parallel session fakes.

Attachment fixtures such as tests/brain/toolMediaAttachments.test.ts allocate one temporary instance root and inject its chat-images child, not the root itself. chatFilesDir() derives a sibling chat-files directory from that image path; both namespaces must stay inside the fixture root and be removed together. Passing a top-level temporary directory as the image path instead shares chat-files with other tests, so a sweep can reclaim files absent from its own database.

Workflow engine fakes supply the required host methods even when their capability results are null: captureToolOutputWriter, delegatedTurnsOutOfProcess, delegatedWorkflowExpansionAvailable, subagentSessionForChannel, workflowExpansionRpc, delegationCompletionEmitter, toolResultInlineBytes and currentWorkDir. createWorkflowFixture in tests/plugins/workflowEngine/fixtures.ts supplies per-test result files and delegated state; focused background, execution and expansion scenarios provide their own contexts. The single completion sink accepts kind: 'subagent' | 'workflow' and returns an awaited typed durable outcome; a workflow sends only its whole-run summary. Completion acceptance is not delivery proof: only a settled parent custom_message with details.resultId proves delivery, with journal append and inbox acknowledgement in one transaction (including failed boot recovery results). tests/plugins/pluginContextDelegationCompletion.test.ts exercises both payload kinds, parent-scope capture and unchanged outcome/error propagation through the real plugin context. Recovery hooks include continueNode, the existing continuation entry point exercised by the models and workflow recovery suites. These fakes install no production hooks or external transport.

tests/plugins/subagentCompletionSpill.test.ts runs the real loaded Subagent plugin through captured output writers, the immutable spill store, bounded formatter, completion ingress and PostgreSQL parent journal. Its twelve cases cover child results and whole-workflow summaries in host and managed session namespaces: an oversized UTF-8 body, a body one byte below the live limit, and an explicit spill-write failure with a DelegateRead recovery hint. It holds the parent turn open through injection and journal commit, compares the complete file bytes and excludes the unread tail from the parent journal. PI execution and managed transport are fixtures; host filesystem writes and database commits are real.

The real-daemon delegation harness includes scripts/tests/delegate-e2e/midturn.mjs and its held scripted provider in midturn-model.mjs. It runs single-result, simultaneous-result, whole-workflow-summary, user-steer FIFO and large-result spill scenarios with runtime.subagentRunnerEnabled both enabled and disabled (runner cap 2). Provider arrivals and terminal child/workflow SSE frames release each boundary; there are no sleep or polling barriers. Assertions check append order after tool results, one final idle, hidden result bodies, completion badges and commit acknowledgement before idle. Run the built harness through its isolated test boundary; --scenario=single|several|workflow|fifo|spill and --pool=enabled|disabled select a focused case, while the default runs all ten. The spill case compares the complete UTF-8 bytes with the parent-session file, verifies that the provider request and parent journal contain only the bounded path/byte-count/preview placeholder, then has the real parent Read a tail line absent from that preview before idle. It covers the host spill namespace; the real-daemon fixture does not provide a managed execution target. DELEGATE_E2E_ARTIFACT_DIR retains request and SSE evidence. The fixture explicitly uses openai-completions and disables tool deferral so its scripted calls exercise the real PI queue rather than a test queue. delegate-e2e/run.mjs includes these scenarios after its existing delegation checks.

Restart scenarios 11 and 12 in scripts/tests/recovery-e2e/run.mjs exercise the same result inbox around a real SIGTERM: a queued result not yet ingested is delivered once after recovery, while a journal-committed result is not replayed. RECOVERY_E2E_ONLY=11,12 selects both against an isolated built daemon. The held provider in result-restart-model.mjs exposes request barriers instead of elapsed-time guesses. tests/brain/brainService/subagentResultRecovery.test.ts also covers both completion kinds, a lost PI queue and committed delivery across compaction.

Complete API and plugin disk fixtures reuse tests/helpers/fixturePlugin.ts writeFixturePlugins(root, specs); manifestOverrides preserves each fixture's declarations and moduleSource copies the complete module verbatim. Discovery-only manifests and malformed inputs stay local, and each suite still owns its roots, ancillary assets, loading options and cleanup.

For process-route authorization tests, setup({ ownerRoutes: true }) loads the terminal and subagent plugins, grants terminal to the ordinary account, and returns the in-memory UserStore as users so a test can create and grant a second administrator explicitly. Without ownerRoutes, no owner-operation plugins or extra terminal grant are installed. These are test-only fixtures with fresh stores per invocation; they do not change production permissions or perform provider requests. p34OperatorBoundary.test.ts exercises the ordinary-account denial and second-administrator success through the real plugin dispatcher and terminal gate.

Terminal capacity tests in tests/plugins/terminalPlugin.test.ts and the real-loader managed reader in tests/plugins/managedConsumers.test.ts inject a complete Sandbox control into the loaded registry. Besides prepareExecution and ENVIRONMENT_CONTROL_METHODS, the provider must expose resolveLocalTarget, bindLocalTurn, localFiles and localRequest; unused methods throw if called. Each fixture asserts that the real registry.control('sandbox') resolves before exercising its held preparation or managed read. A partial registered object remains unavailable under the production shape check, so it cannot stand in for a working provider. The capacity tests retain their real asynchronous preparation gate and background children: finally releases and awaits the held launch, and the existing awaited per-test registry sweep kills children before temporary directories are removed. These fixtures change no loader policy, enabled-plugin selection, grants or production runtime.

The managed recall and skill-catalog scenarios in tests/brain/brainService/{memory,prompt}.test.ts, tests/brain/dreamingFreshRequest.test.ts and tests/api/managedProjectConsumers.test.ts use the same complete Sandbox registration contract and assert live control resolution before the scenario begins. Unused local operations throw. This keeps recall ACL changes, host/managed skill parity, fresh dream Project switches, guest icon validation and managed Git inspection reachable without weakening the registry's shape check. The unavailable-provider cases still remove that control explicitly.

Memory and plugin unit-test fixtures

Migration fixtures replay the shipped SQL ladder instead of pinning a version number. packages/db/tests/migration/*.test.ts copy only the files below the version under test into a temporary directory, migrate, seed the old shape, then add that file and migrate again (packages/db/tests/migration/sessionDevice.test.ts). tests/plugins/sandboxPreservingSchema.test.ts seeds a verbatim plugin ledger of versions 1–20 beneath Sandbox's shipped version-21 baseline, proves a re-run leaves those rows untouched and applies a version-22 migration on top; the file's second case reopens the plugin database over that saved state and checks leases, snapshots, publications and resource history survive. daemonBootWiring.test.ts awaits the maintenance stop handle before reading retention results; that existing handle acknowledges the in-flight asynchronous writer commits.

The dreaming scenarios in tests/brain/dreamingService.test.ts await MemoryStore.update when simulating concurrent user edits. Their runTurn callbacks complete only after those writes commit, so batch completion observes the actual edited rows rather than queued writes.

tests/helpers/memoryEmbedding.ts owns FakeMemoryEmbeddings, a table-backed fake used by the six memory ranking and recall suites. Known texts return fresh float vectors; unknown texts return three zero dimensions, or two when requested by the vitality suite. Its failFor option rejects only single-text embedding with embed boom; batches still use the table. Snapshot and memory-space identity come from fakeSnapshotConfig and fakeMemorySpace in the same file (memoryEmbedding.ts:6 and :10), which twelve embedding and brain test files import. It makes no network requests and has no production caller.

directEmbedBatch(embeddings) in the same file adapts a fake service's embedBatch to the worker's batch function, answered in-process, for suites that do not fork the background worker (memoryEmbedding.ts:46). A failing request fails the whole batch, as an outage would. Four test files use it: api/memoryRoutes.test.ts, api/memoryVitality.test.ts, brain/memoryMaintenanceService.test.ts and embeddings/embedQueue.test.ts.

tests/helpers/fixturePlugin.ts owns disk plugin preparation for loader, controls, alerts, daemon boot, update preflight and UI-route tests, and also config, system readiness, plugin skill manifest, licence snapshot, publisher gate, apply-change and API transition tests. writeFixturePlugins(root, specs) creates each plugin directory, writes the default manifest and then applies manifestOverrides after declaration blocks. The default manifest is name, version 1.0.0, apiVersion 2, a fixture plugin <name> description and entry index.mjs (fixturePlugin.ts:40-46). Each spec supplies either a register body, wrapped in the existing function template, or moduleSource, copied byte-for-byte without source inspection. The optional provides, configSchema and userConfigSchema fields are written into the manifest verbatim (fixturePlugin.ts:8-18). For example, loader tests provide complete modules and set the manifest version, description and apiVersion through manifestOverrides (tests/plugins/loader.test.ts:23-34). Without overrides, existing defaults stay unchanged; deliberately malformed JSON is still written directly by its test. fixturePlugins loads an authoritative test context with in-process delegation, matching the existing context defaults. Extra assets, temporary roots and cleanup remain owned by callers; these fixtures never load installed plugins or run on normal daemon boot.

Minimal use, with cleanup in finally:

const fixture = fixturePlugins([{ name: 'ledger', register: 'ctx.registerText("system-prompt", "raw");' }]);
try {
  // Exercise the code under test with fixture.provider.
} finally {
  fixture.cleanup();
}

writeFixturePlugins(dir, specs) is the variant for a plugin root that already exists, such as a folder that appears while the daemon is running. It is used by daemon/daemonBootWiring.test.ts and update/apiTransition.test.ts. Use fixturePlugins when the test only needs a fresh root.

  • The test-only tmux driver exposes stopMock(child) for the CLI tmux scenarios. It ignores absent or already settled children, sends SIGTERM, waits up to one second, then sends SIGKILL if necessary. It clears its timer and exit listener. Session/server cleanup and owned temporary-directory removal stay in each scenario; cli-tmux-signals.mjs is a consumer.
  • daemonHarness.readDaemonLog(logDir) joins sorted daemon-* files and queryDaemonPid() returns the trimmed systemd MainPID output. Both propagate errors; each scenario retains its current unavailable-log/service and zero-PID policy. These helpers do not own boot, restart or teardown. Integration consumers are recovery/delegate diagnostics and the API/web PID safety checks; their migration must land with the helpers.
  • daemonHarness.configureInstanceMcp(baseUrl, token, name, args) creates an enabled instance-scoped stdio server using process.execPath, then reads the instance list and requires connected status. It checks both HTTP responses and exposes HTTP failures. It acts during parity/lazy-MCP preparation, never production boot. No server is configured unless a scenario calls it; lazy MCP is a current consumer.
  • sseStream.openEventCollector(baseUrl, path, token) collects parsed events in order, exposes connected after the subscription comment and retains deadline-bound predicate waiting with event evidence. abort is immediate; close waits for reader settlement. Scenario callers select their existing close policy and bound readiness waits. No stream is opened without a call; events are retained for the stream lifetime. Integration consumers are the API, brain and delegate harnesses, whose adapters must migrate together.
  • sseStream.watchIdle(baseUrl, token, session) preserves the raw-line idle counting shared by parity and lazy MCP. stop uses the existing bounded stream close. It does not parse JSON or change each suite's turn deadline; without a call there is no subscription. Both MCP suites consume it.
  • scriptedOpenAi.completionFrames({ id, created, model }) returns delta(value, finishReason) and usage(prompt, completion) SSE strings with the existing OpenAI completion envelopes. Metadata and token counts remain supplied by the scenario; stream headers, terminal markers, routing, held responses and socket lifetime remain separate. It has no network side effects. Parity and lazy MCP consume it; brain/hooks/workflow model migrations use the same seam.
  • Tmux evidence uses one ORDINARY_FRAME_LIMIT_MS policy of 50 ms for both raw frames and aggregate summaries. analyzeFrameDiagnostics(frames) and validateFrame(frame, prefix) have no budget override. aggregateTmuxReports accepts expectedRounds, repo, expectedCommit and expectedDistHash; it snapshots Date.now once and uses a fixed one-hour evidence window plus one-minute future skew. Forced frames retain their existing treatment. No evidence is checked until an analyzer runs; cli-tmux-support.test.mjs and the aggregate runner are consumers.

Shared browser fixtures

ChatPage in web/tests/e2e/pages/ChatPage.ts scopes shared chat controls to the full surface by default. Pass new ChatPage(page, 'compact') for the advisor dock; the existing data-variant attribute and visibility select the interactive surface without changing navigation. Hidden server copies retained during Next hydration do not match shared controls; multiple visible surfaces still fail readiness. After a settled run folds, call openWorkRuns() before asserting interim text or tool output. It waits for the summary and opens closed runs through their real buttons; unused page objects perform no browser work. chat.long-tokens.e2e.ts uses both scopes to retain its prose, code and horizontal-overflow checks inside expanded work. users.project-permissions.e2e.ts waits for each grant PATCH acknowledgment before closing its context, so the next viewport checks the persisted final state rather than an interrupted autosave.

Browser specifications share collectErrors(page) from web/tests/e2e/fixtures/designReview.ts. Register it before navigation or interaction and assert the returned array after the flow. It records subsequent console errors and uncaught page errors only; it does not classify requests or expected HTTP failures. Without a call no listeners are registered. chat.upload-progress.e2e.ts is a consumer; richer plugin request diagnostics remain with collectPluginErrors.

documentOverflow(page) in designReview.ts evaluates document.documentElement.scrollWidth minus document.documentElement.clientWidth once and returns horizontal overflow in CSS pixels. Callers keep their own zero- or one-pixel tolerance; pluginSettingsContract.noOverflow uses the one-pixel threshold. The helper has no observation or polling when unused. Compound document, region and offender geometry remains atomic in sidewaysOverflow and scenario-specific evaluations. Measurements against window.innerWidth remain viewport measurements.

The fake daemon's sseFrame(event) in web/tests/e2e/fake-daemon/emitters.ts accepts the production BrainEvent and ElowenEvent unions through type-only imports. It formats one non-replay SSE frame with event.type as the event name and JSON.stringify(event) as the complete payload, without an id or replay cursor. handlers/brain.ts and handlers/core.ts call it when emitting their events; core recipient filtering stays in the route. The helper neither subscribes nor emits when unused. Control and conversations streams with different payload shapes retain their own frame builders.

Guides browser acceptance

web/tests/e2e/specs/guides.e2e.ts reuses the existing fake-daemon/Next harness. Its home/category/article matrix covers cs/sk/en, both Studio skins, 320 px touch and 1440 px desktop, and administrator/ordinary profiles. The sample slugs web-ui and projects-workflow are authored manual pages, not test copies. Catalog/article/asset/search handlers delegate to DocsSearchService, which owns the shared document renderer, locale selection, stable headings and lexical ranking. The fixture disables embeddings explicitly; an attempted vector/provider call throws instead of reaching a real service. No database or daemon starts.

GuidesFixture in web/tests/e2e/fixtures/guides.ts configures the daemon-only /__test/guides channel before navigation. Select catalog/article/search/ask faults, optionally for one exact query; hold: true waits for release() rather than sleeping. state() records started/completed reads and held count. Clear the hold configuration before release() when subsequent mounts must complete normally; release settles current reads, not future reads matching the same fault. Changing a query while an earlier request is held exercises browser cancellation and obsolete-reply handling. The ordinary harness reset releases pending reads, replaces their recorder and clears fault/content state between tests. With no configuration all reads use the real local corpus; emptyCatalog alone projects an empty article list for the empty-category case.

Only AI generation is scripted: localized neutral text passes through parseAskReply and citations resolve actual retrieved identities. Provider, account-refusal and rate-limit fixtures exercise UI states, not real model authorization or inference. Product HTTP schemas remain shared imports; these adapters are not evidence that the daemon routes, BFF or installed package work.

An optional relatedUpdate: { slug, sectionId } writes neutral cs/sk/en note files into one owned temporary directory, then invokes the changelog owner's loadEntries and registerRoutes. Files are removed after loading; no announcement is added to shipped entries. Normal plugin listing/bundles still determine availability. seenWrites() verifies that fetching article backlinks does not acknowledge updates. Without this option the existing changelog fixtures are unchanged.

The focused spec checks shared page/palette results, explicit clickable AI sources, direct bookmarks/refresh/history, screenshot decoding and overflow, English fallback, update round trips, loading/empty/error/retry and stale requests. Focused regressions return to a cancelled Ask question and open a same-article section from either a lexical result or an AI citation. Route unmount/revision cancellation (net::ERR_ABORTED) is expected; other failed docs/search transports and all HTTP failures fail the navigation matrix. Test screenshots are local Playwright artifacts, not committed assets.

web/tests/e2e/specs/guides.screenshots.e2e.ts captures neutral English home/article views at 1440 px and 390 px and opens the real conversation Project picker with three seeded managed targets. It verifies switching to another target, but neither creates nor starts a real environment. Run it with the existing harness; set E2E_GUIDES_SHOTS to a local output directory (default /tmp/elowen-guides-captures). The capture never overwrites shipped documentation images. Visually inspect guides-home.png and project-picker.png before copying them into the respective docs/site/images/web-ui/ and docs/site/images/projects-workflow/ directories. Both samples reference those same assets from cs/sk/en with localized alternative text. The viewport-specific home/article captures remain local review artifacts.

A collected spec is preparation only: run it against the integrated renderer, corpus, API/client, content, Guides UI and changelog revisions before claiming browser acceptance. Production transport, deployment and provider behavior are outside this fixture's evidence.

Chart unit-test teardown

web/tests/fixtures/recharts.ts exposes cleanupCharts(): await it in a suite's afterEach before restoring timer or frame mocks. It unmounts React trees, then completes one animation frame so Recharts' queued Redux notifications cancel their Node fallback timers before jsdom removes browser globals. Native frames use a frame-completion barrier; virtual enrollment clocks advance jsdom's 60 Hz scheduler. There is no fixed real-time delay, error interception or global setup hook.

Tests with an owned frame queue pass its pump explicitly: await cleanupCharts(() => { pumpFrames(); }), as ProjectCard.test.tsx does. ElowenCliSection.test.tsx uses the native barrier. The other suites rendering real host charts, resource meters and plugin resource surfaces use the same fixture; fully mocked charts and pure data helpers do not need it. It does nothing unless called, and does not replace a suite's MSW, QueryClient or other resource cleanup. web/tests/fixtures/recharts.test.tsx verifies native, virtual-clock and controlled queues, including zero remaining Redux frame callbacks and fallback timers.

Fetch-SSE unit fixtures

web/tests/fixtures/brainChatStream.ts supplies real response bodies consumed by readDaemonStream. In a suite's beforeEach, call installDaemonStreams(policy) with its explicit finite retry budget; it starts a fresh authentication generation, intercepts only the policy bootstrap and three daemon feeds, and restores fetch after each test. All REST traffic still reaches that suite's MSW handlers or client mock. Without installation it does nothing; native EventSource is not a product transport. BrainChatController.test.tsx exercises this boundary through the real provider. Await emit(event, payload) for a React-flushed frame; use queue inside one async act for frames racing cancellation. A feed is ready only after its authoritative snapshot, not after HTTP headers; end() closes the body and consumes the owner's retry budget. Cancellation closes the actual reader, so enqueueing after cancellation is invalid. The fixture owns no server, timers or product policy defaults.

web/tests/fixtures/mcpConnection.ts supplies the canonical MCP connection observation for unit-test server rows. Callers select transport and readiness explicitly; ChatTelemetry.test.tsx uses it for live, sleeping and disconnected servers. It starts no transport and supplies no admission or retry policy.

Connection-state browser fixtures

web/tests/e2e/fake-daemon/connections.ts owns connectionPolicy() and computerConnection(projectId, connected). Policy derives from the fixture's stored config and canonical limits; authenticated /connection-policy, the initial retry-policy core event and committed config events expose that same revision, 60000 ms cap and no jitter. Computer seeds supply typed server observations with retry: null, not an invented remote episode; callers can explicitly seed stale or missing observations. These helpers do no work outside the fake harness.

handlers/brain.ts includes modelRetry and executionState in authoritative control snapshots. Emitting a typed retry frame also updates that control before resync/reload, so a later snapshot cannot revert a live retry to an older seed. connections.retry-state.e2e.ts consumes these seams in 36 full-matrix cases: cs/sk/en, 320/1440 px and both Studio skins. It checks independent transport/model/ computer-wait lanes, status after resync/reload, live prose and unsent input across resync/background, initial loading, bounded 503 retries, exhaustion, keyboard manual retry and restored-auth recovery. Countdown changes stay outside live announcements; stale/unknown/offline observations expose neither remote retry buttons nor counters. Touch contexts are real Chromium isMobile/hasTouch; background/resume and auth refusal/restoration are simulated, not physical OS suspension or external login.

Existing Settings specs retain the canonical millisecond payload, 2/4/8-second preview, zero retry budget and always-on exception. Account/Project/dashboard and Terminal specs consume current typed status presentation. Desktop coverage uses the native loopback RFB fixture and real noVNC client: a held, expiring ticket exposes connecting state before existing framebuffer pixel/size assertions. MCP seeds expose SDK-owned null metadata; they do not observe a live SDK's hidden retry attempts. Resync has no blanket harness-noise exemptions. Fault cases allow only their exact injected route/status errors; the MCP error-state assertion waits up to 20 seconds for existing native request retries, without changing runtime policy.

mcp.register.e2e.ts retains its existing per-spec request-failure collector. It classifies only GET /api/connection-policy with an empty query and exact net::ERR_ABORTED, observed before the first policy HTTP 200, as bootstrap cancellation. The flow explicitly permits at most one such cancellation, requires a nonempty set of policy responses and asserts every response is HTTP 200. Other methods, paths, queries, failure kinds and aborts after the first HTTP 200 remain unexpected failures; existing console/page-error and final unexpected-network assertions are unchanged. A separate causal diagnostic traced the first abort to useElowenEvents effect cleanup, followed by a new effect lifetime and a successful policy request, not a timeout or auth callback. The higher-level React cause, including StrictMode, is not established; this test-only classification introduces no runtime fix. After paging back to default order, the ownership check expands the native Instance Settings group through its accessible button and requires aria-expanded=true before inspecting its label.

The historical full touched-flow matrix ran once: 120 of 129 passed, nine failed. The route-specific MCP error-state case subsequently passed at both 390 and 1440 px. After the runtime ordering/header fixes, the two English 1600 px computer-ring cases passed in both Studio skins with their original geometry assertion. A separate final MCP run passed all six register flows: cs/390, en/1440 and sk/320 in both Studio skins, with zero retries. Its exact six-case selection was listed and inspected before execution; a preceding anchored selector selected zero tests, not six runtime failures. All six flows passed loading/empty/search, global sort, drawer/auth, edit/toggle/bulk, native expansion/ownership, card-width/overflow/touch-target and final console/network assertions. Each trace observed one bootstrap cancellation and one policy HTTP 200. These focused passes clear the earlier two-ring/six-MCP blockers; they are not a fresh full-129 pass. The 36-case connection-state matrix remains separate passing source-browser evidence.

The candidate-bound typecheck:e2e passed. Plain-host compilation still resolves old database declarations and fails; that resolution failure is not a remaining E2E-source type defect or a passing plain-host gate. At that candidate, standard e2e:look was blocked by its networkidle wait on permanent fetch-SSE. The separate visible-readiness capture passed 48 screenshots; it is not a standard-look pass. The current look waits for finite requests and a 500 ms quiet interval, removing only responses whose actual MIME type is text/event-stream from that wait. A later focused standard look passed eight Account Security/Chat shots at 320/1440 px in both Studio skins in Czech, without console errors, failed requests, overflow or lingering visible loading. This does not rerun the historical MCP capture. Actual MCP register/sorted-tools screenshots were inspected across the six cases. Those historical checks do not establish coordinated packaged-artifact, physical-device or external-reauthentication acceptance.

A later focused browser proof passed four English cases with zero retries: real 320 CSS px mobile/touch and 1440 px desktop, each in Studio OLED and Studio Light. The real MCP and WhatsApp bundles loaded through the plugin loader with their complete SHA-256 verified. Captured public GET bodies were forwarded unchanged: MCP retained its 60-second delay, and unlinked WhatsApp retained its 30-second delay, enabled Pair device control and no Unpair control or pairing POST. Changing Date alone left remote countdowns absent and announcements stable. An actual HTTP 200 SSE EOF exercised native readDaemonStream: the local visual countdown changed from 10 to 8 seconds while its live announcement stayed unchanged.

Keyboard expansion, pointer/focus states, viewport geometry and screenshots were checked. Browser error collections were empty; initial bootstrap GET aborts were recorded separately and successful HTTP 200 replacements were required. Server public-theme bootstrap still logged HTTP 401 fallback warnings, so this was not a warning-free run.

The capture-based spec remains evidence only, not imported into default CI: it requires external GET captures and a registry checkout. No self-contained CI regression is delivered. This focused status proof does not establish complete application empty/loading/error coverage, a fresh full-matrix pass, coordinated packaged-artifact, physical-device or external-reauthentication acceptance.

Query-plan and runner instrumentation

PostgreSQL repository tests use the isolated cluster and async database harness exported by elowen-db/testing. Query and index assertions target the real PostgreSQL schema. Pool sizes come from DEFAULT_HARNESS_CONFIG. See Persistence for repository transaction and bounded-read rules.

Runner IPC tests use FakeChild from tests/subagent/fakeChild.ts to record daemon frames and kill signals without forking a brain. Instantiate FakeChild, pass child.asChild() to the host's fork seam, and drive replies through child.reply(frame). kill records a signal but does not emit exit; die defaults to code 1, sets connected=false and emits exit. Nothing happens unless a test drives the fake. The fake has pid 0 and starts no operating-system process; it only models the IPC/event surface used by these tests. Scenario-specific boot, turn and finish helpers stay local. queuedAbort specializes die to code 0, while elowenApiRpc preserves its no-op kill. A real consumer is tests/subagent/runnerHost.test.ts.

Shared brain, alert and fork fixtures

alertServiceFixture(store, catalog) in tests/helpers/alertService.ts builds a real AlertService over a caller-owned store and catalog, with one administrator and no project members. Every push title is collected in the returned sent list. The update and maintenance alert suites (tests/update/report.test.ts and tests/daemon/maintenanceAlerts.test.ts) use it. The caller still owns the database and any temporary directory.

createForkedChildHarness() in tests/helpers/forkedChild.ts returns a controllable child for a node:child_process fork mock. Each suite registers its own vi.mock inside its vi.hoisted import, and the child records its kill signals and sent messages. Its pid is 0, so code that renices by pid never touches the test process. tests/brain/backgroundWorkerFork.test.ts, tests/brain/embedBatchWorker.test.ts and tests/daemon/bootRecoveryChain.test.ts are consumers.

spawnerHarness(runtime, registry, toolAuthority?) in tests/helpers/spawnerHarness.ts builds the brain spawner over a caller's model runtime and plugin registry. childScope(allow) in the same file returns the frozen boundary of a delegated child whose tool allow-list the test supplies. The spawner tool-policy and skill-loading suites share it.

projectSwitchFixture(beforeApply) in tests/helpers/projectSwitchFixture.ts opens an in-memory store with a session and two host Projects. The optional beforeApply callback runs before a switch commits, and the notice and refusal suites share the fixture.

expectSamePromptPrefix(previous, next) in tests/helpers/promptPrefix.ts asserts that a later provider request keeps the earlier system prompt, tool list and messages as an unchanged prefix. The cache-respawn suites use it.

bootstrapUsageRegistrations(keyExpression) in tests/helpers/bootstrapUsageRegistration.ts parses src/daemon/bootstrap.ts with the TypeScript compiler and returns every providerUsage registration whose computed key matches, with its initializer. The calling contract test states how many matches it expects, so nothing is picked silently and nothing is matched by regex.

Private brain characterization boundaries

These exports are private test boundaries, not plugin extension points. Their production logic stays in its owning module; importing them does not register a hook, start work, or add a fallback. No test-specific behavior is selected at runtime.

  • Conversation targets: call isEligibleConversationTarget(row) to characterize ancestry and session-kind eligibility. The host projection uses it during list and resolve; emptiness and account visibility remain separate checks. tests/brain/conversationTargets.test.ts imports the predicate directly. Without the test import, createConversationTargets still uses the same predicate.
  • Dashboard digest: buildDigestPrompt(input), parseDigestReply(text), and shapeDigestPayload(raw) expose the generator's existing prompt, parsing, and cleanup stages to tests/brain/dashDigest.test.ts. DashDigestGenerator.run calls these stages during one background inference; a reply that cannot be parsed returns null and the generator marks the row failed. Helpers do not perform inference or persistence themselves, and existing prompt and store budgets remain unchanged.
  • Delegated prompt truncation: PROMPT_TRUNCATION_MARKER exposes the exact marker used by packDelegatedPromptAppend when a section exceeds its fair share of the existing prompt budget. tests/brain/platforms.test.ts and tests/plugins/subagentContext.test.ts assert that shortened sections remain visibly marked. Sections that fit are not marked; importing the constant performs no work.
  • Embedding worker: embedBatchInWorker(config, inputs, deps) is exported for tests/brain/embedBatchWorker.test.ts to characterize IPC replies, timeout, stopping, and usage. The production entry is embedBatchThroughWorker. Its dependency injection permits a fake fork and bounded test deadlines; absent overrides use the real fork and existing deadlines. Stop admission, malformed replies, and whole-batch failures reject rather than fabricate vectors.
  • Tool reason description: REASON_DESC is private to toolReason.ts and exported for the schema assertion in tests/brain/toolReason.test.ts. withReason applies it when composing eligible local object schemas; excluded tools remain unchanged. Importing the description does not augment a tool or execute a call.
  • External tool caps: MAX_EXTERNAL_TOOL_BYTES and MAX_EXTERNAL_DESCRIPTION_BYTES expose the existing 8,000-byte definition and 1,000-byte description bounds to tests/brain/toolSchemaCap.test.ts. capExternalToolSchema applies these bounds during tool composition only when registration metadata marks a tool external; internal tools retain identity. Oversized schemas become permissive object schemas while the external owner still validates calls. The constants do not expose configuration or perform work.
  • Vision fallback: decideVisionHop(input) remains the production export; VisionHop is a module-private result type. service/visionHop.ts consumes the decision through ConversationLifecycle.maybeVisionHop, called by BrainTurnRunner.send under its session lock. A configured distinct vision route can request an in-place respawn for an image turn, and a text turn on the fallback requests a return. No eligible transition yields action none; the pure decision itself never touches a session.
  • Goal completion: judgeGoalCompletion(text) owns the explicit start-of-line GOAL_DONE evidence contract used by GoalLoopService. Placeholder echoes and fenced examples do not complete a goal. Its contract comment belongs directly above the function; the placeholder helper keeps its own separate explanation. No sentinel yields done false.
  • Workflow terminal projection: terminalizeWorkflow(run) is shared by WorkflowResumeRecovery.resumeWorkflow after unsuccessful recovery and service/statusService.ts after a workflow is found not live. Recovery publishes the terminal state; the status reader projects it without writing. Finished and errored nodes retain their states, other nodes become error, and the top-level projection is cancelled. Successful recovery and live workflows do not use this projection; a daemon restart alone is not proof that a journaled DAG is unrecoverable.

Provider-brand regression fixtures

settings.provider-brand-icons.e2e.ts uses the existing design/seed harness with error collection before navigation. Old remote candidates receive their blocking policy or wrong content, rather than a generic successful SVG. Custom API endpoints require the visible inline glyph in both row and drawer; individual model marks retain exact same-origin asset-byte assertions. Request and console diagnostics remain unfiltered. Retain actual Chromium errorText and classify ORB only when observed.

Required integration checks cover both Studio skins, cs/sk/en, desktop and real touch mobile, endpoint changes, loading, API errors, retry and empty statistics. Run the complete provider-brand and provider-model scenarios against the combined implementation and inspect screenshots; fixture assertions alone do not establish a passing browser matrix or production web build. See the production contract.

Ephemeral computer baseline and physical-device checklist

scripts/tests/computer-recovery-e2e/run.mjs prepares only an acknowledged-mutation baseline on an ephemeral Linux/x64 paired executor. It reuses recovery-e2e/paired-executor.mjs and computer-model.mjs, not a separate executor or recovery framework. It performs one acknowledged Bash append and one model-side Read, checks one side effect, and retains the original native Project target and journal tool-call identities. It introduces no disconnect, retry or restart.

Within the required isolation boundary, with an independently prepared candidate selected by ELOWEN_E2E_DIST, invoke:

node scripts/tests/computer-recovery-e2e/run.mjs --target ephemeral --scenario acknowledged-mutation --output <new-file.json>

The runner never builds or installs. All three options are required; duplicate or unknown options, physical targets, URLs and credentials are not accepted. Output is create-only with mode 0600; an existing file is not overwritten. Evidence records the candidate dist SHA-256 (checked before and after), actual core/client and Files/Terminal versions, host OS, canonical connection observations, milestones, native session/assistant-entry/tool-call IDs and observed executor/effect counts. Unknown counters remain null; server-observed remote retry stays null, not an invented retry counter. Failure evidence retains the failed stage and error name, not raw errors that can contain ticket URLs.

evidence.test.mjs contains six pure Node checks for parsing, help without candidate startup, missing-candidate evidence, create-only output, strict evidence validation and canonical connection observations. These source checks do not execute the real-daemon baseline. The baseline itself is prepared, not E2E-verified. Even a future baseline status: passed keeps both recoveryAcceptance and physicalAcceptance literally not-run.

Physical-device acceptance remains a separate, unexecuted checklist:

Device Acknowledged mutation Loss/retry/restart Physical acceptance
Linux PC not-run not-run not-run
Windows notebook not-run not-run not-run
  • Record each real device's OS, actual core/client/plugin versions and candidate identity.
  • Verify the original native Project and journal identities, one mutation, model-side verification and one side effect on each device.
  • Run the separately authorized loss/retry/restart matrix on each device and retain actual connection observations and durable results; unknown counters stay unknown.

The ephemeral Linux/x64 fixture is neither physical Linux acceptance nor Windows emulation. The runner has no physical target or loss/retry/restart scenario; source checks and a baseline pass cannot complete this checklist.

Public declaration tooling

Public plugin-shared declarations consumed outside core are exact entries in the packages/plugin-shared workspace of knip.json. For example, add gitSha.d.mts beside the existing runtime *.mjs entry when publishing the adjacent exported module and declaration. The same model covers httpClient.d.mts, waitUnlessAborted.d.mts, singleflight.d.mts, configScalars.d.mts, alertPushPrefs.d.mts and the shared message catalogue's messages.d.mts. This applies only during static analysis and changes no runtime loading or plugin registration. An omitted declaration remains unmodeled as a public entry; do not replace its entry with an ignore. tests/contract/publicDeclarationEntries.test.ts checks the real published subpaths, shipped declarations and exact tooling entries. The web workspace has no usage-summary analysis exemption; the retired lib/effectiveSpeed.ts mirror is removed.

Registry boundary fixtures

The registry's onedriveIncompleteScan test injects managedFor: async () => null for a host mirror and extraIgnore: [] for no additional ignore rules. SyncEngine requires this resolver even when no managed mirror is present; null selects the existing host path. The fixture keeps real storage and synchronization while forcing scanLocal.complete to false, proving that an incomplete scan preserves baselines and performs neither uploads nor deletions.

The githubP58Flow fixture uses the real confirmation store and a mocked device-login starter. Repeated GitHubService.confirm calls for replace_identity reject with invalid_action and status 400 without consuming consent; startDeviceAuth then accepts that same token and consumes it. A changed identity snapshot is rejected and consumed only by device authentication. These fixtures do not contact GitHub or migrate customer secrets; github.test.ts covers the corresponding HTTP route.

The registry repository's test-only SelectMenu in tests/ui/host/pickers.tsx exposes value, onChange, options, label and optional disabled. Without disabled it remains interactive; disabled locks the native select. It has no invalid option or aria-invalid projection. Registry plugin UI tests install it through tests/ui/hostRuntime.tsx, including GitHub Project tests. This native-select fixture checks selection and accessible names, while the real host's Radix keyboard, focus and styling contract requires browser acceptance.

Shared scripted transports and fixtures

sseStream.openSessionStream(baseUrl, token, session) returns { state, close }; state counts idle/compacted events and retains error messages/workflow events in arrival order. Minimal use is const { state, close } = await openSessionStream(baseUrl, token, session). Without a call, no stream opens. close aborts immediately without awaiting the reader; callers own predicates/deadlines. Continuity, hooks, subagent and workflow suites import it as openStream. managedWorkflowModel consumes readJson, contentText, startCompletionStream, endCompletionStream and completionFrames from scriptedOpenAi rather than duplicating the wire.

tmux-evidence/diagnostics.percentile(values, fraction) is the shared nearest-rank helper used by CLI pipeline/render benchmarks. percentile(samples, 0.95) returns zero for empty input; ordinary fractions are greater than zero and at most one.

Plugin tests use typed lookup/invocation from tests/helpers/pluginTool.ts, keeping call IDs, result types, scopes and execution contexts at the caller. docsEmbedder.ts supplies the deterministic search vocabulary; suites own batch accounting. tempDirs.ts registers per-suite cleanup while callers own DB/environment teardown order. environmentRuntimeFake.ts shares machine/disk state and base methods; transport, socket, readiness and resource overrides stay explicit. Importing it installs neither cleanup nor a module mock.

Monaco preparation checks source availability before touching public output. copyMonaco.test.ts executes the actual script in temporary fixtures, proving a missing installation preserves the destination. Root web compilation still reaches .ts-importing E2E helpers through Vitest dependencies; excluding entry globs does not remove those edges.

Production analysis and platform identity indexes

Production analysis includes scripts/**/*.mjs! and uses !scripts/tests/**! to exclude test harnesses while retaining existing entries and ignoreExportsUsedInFile. tests/contract/platformIdentityContract.test.ts opens an isolated PostgreSQL database through tests/api/postgres.ts and inspects pg_index, pg_class and pg_namespace for elowen.user_settings. For every platform descriptor, it checks the pinned index name, uniqueness, validity, the single value key column and the partial predicate matching that descriptor's link-setting key.

Persona wording contracts

Persona wording contracts use personaTemplatesFor({ scheduled: false, ownerChatShape: true }).parts to select the shipped default owner persona. renderTemplate.test.ts and promptTemplateContracts.test.ts retain their own wording assertions; promptAdoptedGuidance.md independently pins required guidance and must be updated explicitly when wording intentionally changes. The plugin-tool reference contract checks the unique default interactive parts, the returned platform overlay when present, and scheduled parts. With no overlay, no overlay template is checked. Plugin-selected work templates remain outside this default-template contract. personaRoute.test.ts owns exact part order and plugin work-template substitution; wording tests do not introduce another route resolver.