# Testing Agent Knock Knock keeps every deterministic, integration, compatibility, and regression test, but it does not charge the full process-heavy suite to every edit. The canonical classification is `test/test-tiers.json`. A manifest test and every tier runner fail before execution if a test file is missing, duplicated, or unclassified. `npm run architecture:dashboard` runs the same fail-closed architecture gate as `validate:architecture` and prints the current health snapshot. It reports total production LOC without gating it, while cycles, hard/default function violations, large-file concentration, semantic tool counts, and bundled Skill synchronization have explicit non-regression budgets. See [`docs/architecture/current-architecture.md`](./architecture/current-architecture.md). ## Test tiers | Command | Purpose | When to run | | --- | --- | --- | | `npm run test:fast` | Deterministic unit/component tests without test-level child processes | Every development, debugging, refactoring, review, and local-install loop | | `npm run test:integration` | CLI subprocess, monitor, SQLite, Store locking, lifecycle, installer, and compatibility fixtures | Release-gate component; do not run during ordinary development or local verification | | `npm run test:affected` | Complete fast tier plus mapped integration tests; unknown or shared-core changes run the full tier | Maintainer/release diagnostics only; not an ordinary development command | | `npm run test:full` | The exact union of fast and integration tests | Immediate pre-publication gate for an actual npm or ClawHub release | | `npm test` | Compatibility alias for `test:full` | Same release-only boundary as `test:full` | | `npm run test:release` | Full suite, isolated current-build OpenClaw compatibility check, ClawHub runtime validation, and ClawHub publish dry-run | Immediate pre-publication gate for an actual npm or ClawHub release | | `npm run test:release:live` | The release tier plus credentialed native Codex and Claude lifecycle smoke/attestation | Optional release-time diagnostic with dedicated prepared tmux panes | | `npm run compat:openclaw` | Full minimum/current/API-boundary OpenClaw compatibility matrix | Periodic maintainer check and any change to declared compatibility floors; not required for every package release | During normal development, debugging, refactoring, review, local installation, and local verification, run only `npm run test:fast`. Type checking, builds, architecture/evidence validators, installation, and non-test health checks may be run when relevant, but they do not authorize a broader test tier. Run the full/release suite only immediately before an actual npm or ClawHub publication. A local OpenClaw install is not a package release. The live release tier can make authenticated coding-agent turns. It is never selected by `npm test` or `test:release`; opting in is an explicit operational decision. The ordinary release tier builds in temporary state and does not control a real coding-agent pane. Routine releases exercise the OpenClaw version pinned by `openclaw.build.openclawVersion`. The slower minimum Host, minimum Plugin API, and adjacent unsupported boundary checks remain available through `npm run compat:openclaw` for periodic compatibility review. Run the full matrix whenever a declared OpenClaw compatibility floor changes. The live gate requires both complete panes, a new private evidence path outside the repository, the environment opt-in, and the command-line confirmation. It forwards the exact arguments to the single-attempt lifecycle runner and then verifies the resulting Codex+Claude matrix evidence: ```bash AKK_RUN_LIVE_LIFECYCLE_SMOKE=1 npm run test:release:live -- \ --confirm-live \ --codex-target \ --codex-expected-pane-pid \ --codex-expected-version \ --claude-target \ --claude-expected-pane-pid \ --claude-expected-version \ --evidence ``` The runner builds once per tier and passes an explicit cross-platform file list to Node instead of relying on shell glob behavior. Its default file concurrency is capped at four because each integration worker recursively starts CLI and fake terminal processes; higher values made both wall time and bounded test-fixture timeouts worse on the maintainer machine. Override it with `AKK_TEST_CONCURRENCY` for a controlled benchmark. The runner also gives Node child processes a shared bytecode cache under the operating-system temporary directory. Override `NODE_COMPILE_CACHE` when profiling a particular cache, or point it at a new `mktemp -d` directory for a cold-cache comparison. ## Affected tests `test:affected` builds once, reads changed and untracked paths without shell globs, and always runs the complete fast tier. By default it compares `HEAD` with the current index and worktree. Supply an explicit base to include branch commits as well as staged, unstaged, and untracked changes: ```bash npm run test:affected npm run test:affected -- --base origin/main ``` Known production domains map to no more than five exact integration-tier witnesses. A changed integration test selects itself, a known test helper selects its transitive integration consumers, and a changed fast test or documentation file needs no additional integration worker. An additive `test/test-tiers.json` edit narrows only when every new entry is also a changed test and the prior tier order is preserved. `package.json` plus `package-lock.json` narrow only when their parsed content proves one synchronized version-only change. Deletion, tier movement, reordering, dependency or script changes, and missing content proof select `test:full`. Selection remains fail-closed for an unreadable Git diff, a stale ownership mapping, an unknown path, exact Store/protocol modules, shared production kernels, or selector/architecture authority files. `src/store.ts` and `src/protocol.ts` cannot be narrowed by accompanying tests or manifest proof. This selector remains documented for release-maintainer diagnostics. Under the normal-development policy above, use `npm run test:fast` instead; an actual publication uses the complete full/release gate rather than treating affected selection as sufficient evidence. ## Targeted integration map The map below records which integration witnesses own each subsystem. It is for release-gate triage and failure diagnosis, not permission to run integration tests during ordinary development or local installation. In those workflows, run only `npm run test:fast`. Paths are source paths under `test/`, and the tier runner handles their compiled `dist` paths. Pass one or more exact manifest paths after `--` to run only those integration files. The runner rejects duplicates, unknown paths, and files from the fast tier, so a typo cannot silently weaken the intended check: ```bash npm run test:integration -- \ test/codex-store-adapter.test.ts \ test/stale-bound-resume-cli.test.ts ``` | Changed subsystem | Targeted integration files | | --- | --- | | CLI parser, help, version, doctor, redaction | `test/cli-ux.test.ts` | | Raw/managed send, composer/Enter, acceptance and receipt fences | `test/shards/agent-cli-terminal-send-gates.test.ts`, `test/shards/agent-cli-composer-replay.test.ts`, `test/shards/agent-cli-session-acceptance.test.ts`, `test/shards/agent-cli-receipt-fences.test.ts`; add `test/codex-no-rollout-binding-cli.test.ts` for virgin attachment | | Dispatch authorization, locks, retry and recovery | `test/shards/agent-cli-dispatch-authority.test.ts`, `test/shards/agent-cli-control-locks.test.ts`, `test/shards/agent-cli-dispatch-recovery.test.ts`, `test/shards/agent-cli-receipt-fences.test.ts` | | Callback outbox, responder, retry, Gateway wake and close races | `test/callback-cli.test.ts`, `test/openclaw-plugin-contract.test.ts`, `test/shards/agent-cli-claude-callback.test.ts`, `test/shards/agent-cli-monitor-lifecycle.test.ts`, `test/shards/agent-cli-monitor-recovery.test.ts` | | Monitor completion, approval context, cancellation and renewal | `test/shards/agent-cli-monitor-lifecycle.test.ts`, `test/shards/agent-cli-monitor-recovery.test.ts`, `test/shards/agent-cli-monitor-approval-context.test.ts`, `test/shards/agent-cli-control-locks.test.ts` | | Delegate, workspace and terminal selection | `test/delegate-cli.test.ts`, `test/session-selector-cli.test.ts`, `test/management-cli.test.ts` | | List, status, available actions and terminal discovery | `test/management-cli.test.ts`, `test/session-selector-cli.test.ts`; add `test/codex-no-rollout-binding-cli.test.ts` for binding conflicts | | Codex SQLite/state DB, rollout fd, WAL, candidate discovery and version filtering | `test/codex-store-adapter.test.ts`, `test/codex-no-rollout-binding-cli.test.ts`, `test/stale-bound-resume-cli.test.ts` | | Virgin attach, reconcile-binding, PID incarnation/reuse and orphan recovery | `test/codex-no-rollout-binding-cli.test.ts`, `test/native-thread-ownership-cli.test.ts`, `test/stale-bound-resume-cli.test.ts`, `test/turn-session-binding-cli.test.ts`, `test/shards/agent-cli-dispatch-recovery.test.ts` | | Native New/Clear/Resume transitions and guards | `test/native-thread-lifecycle-cli.test.ts`, `test/native-thread-lifecycle-recovery-cli.test.ts`, `test/native-thread-ownership-cli.test.ts`, `test/native-lifecycle-command-guard-cli.test.ts`, `test/codex-sticky-rollout-lifecycle-cli.test.ts`, `test/stale-bound-resume-cli.test.ts`, `test/codex-no-rollout-binding-cli.test.ts` | | Store, Session, Turn, writer protocol, migration and locks | `test/store.test.ts`, `test/store-protocol-cli.test.ts`, `test/turn-session-binding-cli.test.ts`; add `test/session-selector-cli.test.ts` for projections | | OpenClaw tool schemas, slash routing, supervisor and manifest | `test/openclaw-plugin-contract.test.ts`, `test/management-cli.test.ts`; add `test/install-openclaw-cli.test.ts` for installation/skill sync | | Installer trust, force, update and restart readiness | `test/install-openclaw-cli.test.ts` | | Runtime logs, redaction and permissions | `test/runtime-log.test.ts` | | Lifecycle evidence, verifier and release guards | `test/live-lifecycle-verifier.test.ts`, `test/live-smoke-guards.test.ts` | | Compatibility or legacy identifiers/protocols | The exact domain file plus `test/store.test.ts`, `test/store-protocol-cli.test.ts`, `test/turn-session-binding-cli.test.ts`, or `test/session-selector-cli.test.ts` as applicable | The 11 former `agent-cli.test.ts` domains map one-to-one to exact integration manifest entries; use these names directly rather than a shell wildcard: | Agent CLI domain | Exact manifest path | | --- | --- | | Claude callback and response delivery | `test/shards/agent-cli-claude-callback.test.ts` | | Composer replay and paste-window submission | `test/shards/agent-cli-composer-replay.test.ts` | | Terminal mutation and dispatch locks | `test/shards/agent-cli-control-locks.test.ts` | | Dispatch ownership and authorization | `test/shards/agent-cli-dispatch-authority.test.ts` | | Interrupted dispatch recovery | `test/shards/agent-cli-dispatch-recovery.test.ts` | | Monitor approval context | `test/shards/agent-cli-monitor-approval-context.test.ts` | | Monitor completion lifecycle | `test/shards/agent-cli-monitor-lifecycle.test.ts` | | Monitor/callback recovery | `test/shards/agent-cli-monitor-recovery.test.ts` | | Acceptance and delivery receipt fences | `test/shards/agent-cli-receipt-fences.test.ts` | | Session/Turn acceptance and bookkeeping | `test/shards/agent-cli-session-acceptance.test.ts` | | Raw and managed terminal send gates | `test/shards/agent-cli-terminal-send-gates.test.ts` | The mapping diagnoses a release gate; it never replaces the complete pre-publication full/release suite. Safety fences from #87, native lifecycle smoke tooling from #88, Store upgrade paths, Codex 0.146.0/0.146.1/0.147.0/0.148.0/0.149.1/0.150.1/0.151.0/0.153.0/0.153.4/0.154.0, OpenClaw boundaries, and verified Claude Code 2.1.218/2.1.226/2.1.237/2.1.251/2.1.259/2.1.263/2.1.266/2.1.267 schemas remain covered. ## #206 Terminal Watch fast contract The fast tier owns the deterministic Terminal Watch contract: | Fast witness | Contract proved | | --- | --- | | `test/terminal-watch-store.test.ts` | Owner-private atomic schema-v3 records under `terminal-watches/`, legacy-v1/v2 notify-only normalization, strict interaction aggregate validation, revision CAS, exact provider anchors plus terminal-activity anchors/checkpoints and immutable warnings, and `writer -> per-watch` lock order | | `test/terminal-watch-service.test.ts` | Restart/list recovery, timeout and terminal settlement, actionable/manual questionnaire projection and redraw dedupe, exact observation fences, terminal-activity checkpoint persistence, callback claim-crash recovery, retry, and deterministic idempotency | | `test/durable-notification-kernel.test.ts` | Store-neutral callback/Watch lease, retry timing, exhaustion, and settlement authorization policy | | `test/terminal-submission-acceptance.test.ts`, `test/claude-local-transcript-provider.test.ts` | Preferred exact Codex rollout and Claude current-turn transcript anchors; source-less Codex candidate-set acceptance over anchored plus current roots, including closed-FD frozen-file scans and fail-closed replacement/deletion/multiple-acceptor evidence; drift invalidates an already exact-anchored Watch, while absence or structural incompatibility at manual-Watch creation can be downgraded to a warning and terminal-activity fallback | | `test/terminal-watch-cli-adapter.test.ts`, `test/terminal-watch-callback-cli-adapter.test.ts` | Exact-task capture when available; best-effort `terminal_activity` fallback after observed activity and consecutive stable-idle sweeps; `watch_mode`/`confidence`/warning projection; no Session/Turn ownership; exact-Watch same-controller Status and one-shot `respond_interaction({watch_id})`; managed-first responder arbitration; zero-input release versus uncertain dispatch; activity/manual notification-only behavior; hard failure only for an absent/unobservable exact terminal or unwritable Store; privacy-safe callbacks that never present stable idle as exact task completion; and restart-safe exact fallback anchors | | `test/terminal-list-renderer.test.ts`, `test/openclaw-plugin-helpers.test.ts`, `test/quickstart-docs.test.ts` | Broad read-only Watch discovery, direct user-explicit Watch by exact `terminal_id` even without advertisement, Watch status/unwatch routing and formatting, action-contract v28 semantic-ID-only projection including same-controller status-bound questionnaire response, exact Codex model-control residue repair, and explicit foreground identification, three-axis terminal status, Codex user-priority replace-current-Composer delivery without visibility/exactness or post-text Composer vetoes, automatic fallback Watch callback semantics, Claude empty-only isolation, and the documented terminal selection → exact-task-or-activity Watch workflow | The current public surface has 22 OpenClaw tools. Terminal Watch adds the `watch-terminal`, `watch-status`, `unwatch-terminal`, and `reconcile-watches` CLI entries. OpenClaw's supervisor coordinates managed-monitor and Watch reconciliation in the same non-overlapping lifecycle but with independent error boundaries, so either side can make progress when the other fails. The process-level plugin contract remains in the integration tier and is exercised only as part of an authorized pre-publication full/release gate. The fallback completion-shaped event is deliberately a weaker contract. Tests must assert that it is emitted only after the selected terminal/process has shown activity and then stable idle across consecutive observations, carries `watch_mode="terminal_activity"` and `confidence="best_effort"`, and tells the controller that the signal is not exact task-completion proof. An initially idle or unknown terminal cannot settle until activity has first been observed. The v28 fast contract also proves that structured model actions expose semantic IDs only: Watch uses `terminal_id`; send uses mutually exclusive `session_id` or `terminal_id`; native inspection, new, resume, and reconcile use their documented terminal/session/thread IDs; and approve or handoff retains explicit-confirmation behavior without projecting opaque tokens, fingerprints, revisions, binding IDs/generations, composer digests, draft text, or handoff-only live-native-UUID fences. Questionnaire response uses a fresh same-controller Status projection, one opaque interaction ID, semantic answer IDs or bounded text, and a durable one-shot reservation; changed, manual-required, secret, or uncertain state stays fail-closed. Codex `terminal_user_explicit` proves one clear/inject/paste-wait/Enter sequence even when the Composer is invisible, truncated, unstable, or nonempty, with no post-text Composer veto. Claude user-explicit Send, native inspection, and lifecycle input remain exact-empty-only; managed delivery may require exact empty before input, but its user-explicit Codex Enter is not post-text gated. The Send result contract independently proves physical dispatch, native acceptance, management mode, observation mode, and callback/interaction capabilities. Durable close audit facts come from dispatch receipts rather than human-readable close prose. Candidate-set recovery may leave a provisional Turn pending until exactly one rollout accepts its immutable request hash; it never replays terminal input while resolving that identity. The semantic `native_thread_id` remains public for resume. The plugin/CLI still derives and revalidates those private fences under lock. Store format remains 1 and the Terminal Watch schema is 3; writer protocol 7 fences manual-interaction notification state and bounded callback metadata from protocol-6 writers, while the Watch schema version fences the newer response aggregate. ## Profiling Run the same tier, Node version, concurrency, machine, and compile-cache mode before and after a performance change: ```bash npm run test:profile -- --output /tmp/akk-test-profile.json npm run test:profile -- integration --output /tmp/akk-integration-profile.json ``` The reporter records the commit and dirty state, Node/platform/CPU metadata, concurrency and cache path, total wall duration, counts, per-file worker duration, and every test duration. It prints the 20 slowest files and tests and writes the complete JSON report when `--output` is supplied. Set an explicit concurrency for a controlled comparison: ```bash AKK_TEST_CONCURRENCY=8 npm run test:profile -- --output /tmp/akk-profile-c8.json ``` Do not reduce production polling/settle windows, disable process isolation, or force-exit the runner to improve a benchmark. Split independent file workers, inject clocks/providers where semantics permit it, and retain thin real-process coverage for process, WAL/checkpoint, fs-lock, and lifecycle boundaries. ## #126 callback in-process migration Eight once-per-file executable starts in `test/callback-cli.test.ts` now use the same `parseCliCommand` to `executeCliCommand` path imported by the executable. Their original assertions remain in place. The migration maps each former black-box case to the service invariant it exercises and the process boundary that remains black-box: | Former executable case | Imported service invariant | Retained real boundary | | --- | --- | --- | | Late callback after Turn release | Reject before state or event mutation | Emitted callback argv/stdout/exit goldens | | Corrupted event log | Fail closed on Store validation | Emitted callback error projection | | Failed question notification | Preserve `waiting_for_openclaw` and failed outbox state | Fake OpenClaw Gateway child failure | | Close after failed delivery | Keep the failed outbox retryable and immutable | Gateway child plus separate executable concurrency witnesses | | Reused message ID with changed payload | Reject payload mutation under an existing claim | Retained callback executable helper | | Explicit Gateway URL/token retry | Persist authentication while redacting public state | Fake OpenClaw environment and argv | | Automatic transient retry | Preserve retry state and monitor scheduling | Real retry-monitor and Gateway children | | Manual retry during automatic attempt | Report the exact live lease without mutation | Concurrent winner/loser CLI processes | The dynamic callback test path therefore starts eight fewer CLI processes. The reproducible static evidence moves from 48 to 40 included startup sites (`cli_process` 38 to 30), while all 10 fake-Node startup sites remain. The machine-readable `callback-outbox` migration in `config/public-contract-witnesses.json` ties the old executable witnesses to the callback policy, settlement, and transport service invariants and to the retained CLI, Claude, and OpenClaw boundaries. ## #126 CLI UX and native-ownership in-process migration The second migration slice routes 21 normal invocations through `parseCliCommand` and `executeCliCommand`: eight help, version, doctor, and redaction invocations plus thirteen native-ownership sends. The test and assertion inventories are unchanged. | Former executable cases | Imported service invariant | Retained real boundary | | --- | --- | --- | | Help and version aliases | Exact parser aliases, stdout bytes, and success status | CLI import isolation and copied-distribution doctor executable | | Runnable-doctor failure and public redaction | Probe result projection and secret-free JSON | Copied-distribution doctor argv/stdout/OS-exit plus real-CLI runtime-log redaction | | Codex and Claude native ownership sends | Exact PID/UUID, Store, binding-generation, and stale-ledger authority | Codex attach, handoff, lifecycle recovery, and terminal-send executable suites | The static metric removes the two direct native-ownership startup sites and the shared normal CLI UX startup site, moving the current evidence from 40 to 37 included sites (`cli_process` 30 to 27). All fake-Node sites remain. Claude native inspection was also audited, but remains executable-backed because its PATH-scoped `ps` and `tmux` discovery is not equivalent through the current in-process dependency seam. Crash injection, OS exit, process competition, file-lock, monitor-PID, nested Gateway approval, updater, and terminal-process witnesses in `agent-cli-fixtures.ts` remain process-backed. The machine-readable `cli-runtime` and `terminal-binding-authority` mappings in `config/public-contract-witnesses.json` connect the migrated cases to focused service invariants and retained executable witnesses. ### Doctor capability boundary tier Doctor capability evaluation and invalid-timeout policy remain in the fast tier. The four tests that intentionally execute fake Node and shell programs live in `test/doctor-capabilities-process.test.ts` in the integration tier. They still prove exact argv, version parsing, executable permissions, timeout, non-zero exit, missing executable, and malformed-output behavior; only their tier classification changed. ## #126 management, selector, and Session-binding in-process migration The third migration slice removes the outer CLI process from 50 normal command invocation expressions across 24 tests while preserving all 298 assertion call sites. `management-cli` and `session-selector-cli` now exercise the executable's same `parseCliCommand` to `executeCliCommand` path for list, status, selector, static-terminal, and synthetic-acceptance behavior. The six record-only `turn-session-binding-cli` callbacks use that path for Session authority checks. | Former executable cases | Imported service invariant | Retained real boundary | | --- | --- | --- | | Ten management list/status invocations | Exact JSON projection, trace redaction, static terminal observation, and action-contract v24 | Standalone executable list/status in `store-protocol-cli`, copied-distribution CLI output/exit | | Thirty-four selector, status, send, respond, and approve invocations | Deterministic ambiguity failures, canonical ownership, cross-Store fencing, semantic-ID routing, and private fence derivation | Codex binding and terminal-send executable suites with real terminal observation/input | | Six record-only callback binding invocations | Protocol-3 Session presence, generation, process evidence, route-rename, and protocol-2 compatibility | Callback executable argv/exit, Gateway, retry, and concurrency suites | The static metric removes one CLI startup site from each migrated file, moving the evidence from 37 to 34 included sites (`cli_process` 27 to 24). All 10 fake-Node sites remain. Writer-protocol mismatch and fake-tmux observation in `store-protocol-cli`, callback Gateway/concurrency, Store crash and migration, terminal input, lock competition, monitor PID, and OS-exit witnesses remain process-backed. The machine-readable `callback-outbox`, `terminal-binding-authority`, and `terminal-list-renderer` mappings tie each former executable family to its fast service invariant and to a separate retained real-process witness. ## #126 deterministic dispatch-admission in-process migration The fourth migration slice removes 27 outer CLI process starts across 23 tests while preserving all 63 assertion call sites in those cases. The commands still enter through `parseCliCommand` and `executeCliCommand`; only the redundant Node executable boundary is removed. | Former executable cases | Imported service invariant | Retained real boundary | | --- | --- | --- | | Ten delegate routing tests (14 invocations) | Deterministic agent, workspace, pane-incarnation, and exact-selector routing over the production static terminal/process adapters | Dispatch-authority, recovery, and terminal-send executable suites | | Twelve native slash-command guard cases | Every ordinary send/respond alias rejects before Store creation or terminal input | Native lifecycle and lifecycle-recovery crash/terminal executables | | One static-fixture acceptance case | Synthetic transport cannot become native acceptance without the explicit opt-in | Session-acceptance, receipt-fence, and terminal-send executable suites | The delegate fixture continues to use the production `StaticTerminalProcessSource` selected by `--processes-json`. Its former fake `ps` executable supplied only a deterministic Codex process-birth string and was not a PATH-discovery assertion, so the imported command now injects that same typed observation directly. PATH-scoped process discovery remains covered by its dedicated executable tests. Environment, cwd, clock, output, and exit state are scoped through the CLI async-local runtime; no test mutates process globals. The static metric removes one CLI startup site from each migrated file, moving the evidence from 34 to 31 included sites (`cli_process` 24 to 21). All 10 fake-Node sites remain. Test tiers are unchanged: these filesystem and terminal component cases remain in the integration tier even though their redundant outer executable was removed. The machine-readable `terminal-dispatch-policy` and `terminal-dispatch-ledger` mappings connect the imported cases to focused policy/acceptance invariants and to retained real-process witnesses for argv, OS exit, terminal input, crash recovery, locks, concurrency, monitor PID, Gateway transport, and Store writer fencing. ## #126 callback normal-command in-process migration The fifth migration slice routes all 20 successful invocations that formerly shared the generic synchronous callback CLI helper through `runInProcessCli`, and therefore through the production `parseCliCommand` to `executeCliCommand` path. The 13 affected tests and all 160 assertion call sites remain. Nine tests become async; four were already async. | Former normal callback cases | Invocations | Imported service invariant | Retained real boundary | | --- | ---: | --- | --- | | Record-only creation and duplicate suppression | 3 | Message identity, Turn identity, event persistence, and idempotency | Concurrent duplicate CLI processes | | Direct Gateway delivery and legacy identity derivation | 2 | Gateway payload projection, token redaction, and legacy Session/Turn fallback | Production fake-OpenClaw child processes | | Close, retry, rebinding, and persisted authentication | 7 | Immutable outbox, close-state preservation, binding generation, and URL/token routing | Gateway success/failure helpers and the close-during-delivery child | | Startup reconciliation and accepted-wake settlement | 4 | Retry-monitor launch/reconciliation and no-redelivery settlement | Real detached retry-monitor and Gateway children | | `chat.send`, `agent.wait`, legacy plan, and `sessions.send` delivery | 4 | Accepted delivery plans, run observation, exact argv, and event recording | Production Gateway child-process transport | Environment overlays remain scoped by the CLI async-local runtime. `PATH` and Gateway-token overrides reach the production OpenClaw transport through `cliEnv()`, while the transport continues to start the fake Gateway executable itself. The tests do not mutate `process.env`, change cwd, observe the outer CLI PID, or assert OS exit. The close-during-delivery and callback-retry competition tests still use separate real CLI processes where process identity and file competition are the behavior under test. The in-process `callback` and `retry-callback` commands in this slice complete their delivered or accepted settlement before returning. Reconciliation only persists the PID of the real retry-monitor child it launches; it does not claim an attempt for the in-process caller. No migrated case waits for an outer CLI `attempt_pid` to die, while the manual in-flight and winner/loser lease cases remain on their existing imported-failure and real-process paths. Four explicit callback CLI startup sites remain: Gateway failure and success helpers, the asynchronous concurrency helper, and the direct retry loser. They perform 9, 6, 4, and 1 dynamic CLI starts respectively in this file. The generic helper removes exactly 20 outer CLI starts without removing any Gateway or retry-monitor start. `test/runtime-log.test.ts` also remains process-backed because it proves the executable wrapper's `cli_start` and `cli_finish` records, default `AKK_LOG_DIR` environment routing, and redaction. The static metric moves from 31 to 30 included startup sites (`cli_process` 21 to 20); all 10 fake-Node sites remain. The machine-readable `callback-cli-in-process` witness records the moved normal-command cases, while `callback-cli-boundary` now names the retained winner/loser executable race. ## #126 composer not-accepted in-process migration The sixth migration slice routes the multilingual Codex composer not-accepted command through `runInProcessCli`, and therefore through the same production `parseCliCommand` to `executeCliCommand` path. The test still uses the real tmux adapter executable, preserves the exact multiline composer after paste and after Enter, disables synthetic acceptance, and asserts that AKK dispatches exactly one `C-m`. Its durable not-accepted receipt, close recovery, and public JSON assertions are unchanged; only the outer Node CLI wrapper is removed. The explicitly injected process runner remains a visible `other_process_or_adapter` site and still starts the fake tmux, `ps`, and `lsof` executables with the command-scoped environment. This removes one real CLI startup site without moving or hiding a spawn. The static evidence becomes 29 included sites (`cli_process` 19 plus the unchanged 10 fake-Node sites), a 39.58% reduction from the frozen 48-site baseline. The machine-readable `terminal-composer-in-process` witness records the imported command invariant, while raw/managed send, receipt-fence, crash, lock, acceptance, and terminal-input executable witnesses remain process-backed. ## #126 shared agent CLI in-process migration The seventh migration slice removes the synchronous outer-Node `runAgentCli` wrapper from `test/agent-cli-fixtures.ts`. All 197 former call sites are classified by behavior. Normal deterministic commands now await the shared `runAgentCliInProcess` path, which still executes the production `parseCliCommand` to `executeCliCommand` boundary. Across parameterized tests and shared fixture calls, 214 of the former 233 dynamic outer CLI starts move in process. The remaining 19 dynamic starts use `runAgentCliAsync` because the asserted behavior requires an independently dying PID, exit 86, a live monitor or handoff singleton, cross-process lock competition, or a detached monitor/retry child whose PID is observed. The shared runner scopes environment, cwd, caller PID, wall and monotonic clocks, and sleeps to one imported command. CLI JSON fixtures continue to select the production static terminal, process, Codex-session, Claude-agent, and version adapters. A command that instead supplies a fake `PATH` receives the production `TmuxTerminalControlProvider`, `SystemTerminalProcessSource`, and `CodexStoreAdapter` with one command-scoped runner. Fake `tmux`, `ps`, `lsof`, and `claude` executables therefore remain real adapter subprocesses; their single visible `spawnSync` call site is classified as `other_process_or_adapter`, not hidden or counted as an imported CLI. The 70 affected shard test declarations, the one parameterized native- inspection declaration, and all 1,223 assertion call sites remain. The static metric becomes 28 included sites (`cli_process` 18 plus the unchanged 10 fake-Node sites), a 41.67% reduction from the frozen 48-site baseline. The adapter runner raises the diagnostic-only `other_process_or_adapter` count from 11 to 12 while deleting the synchronous CLI startup site. No production source changes are part of this slice. ## #126 final static subprocess gate The final static slice removes nine more real product-test startup call sites. It does not merge start helpers, move a start to an uncounted file, change the lookahead, drop a test, or alter concurrency. Each migrated case still enters the same production parser/command or domain authority: | Former executable case | Imported invariant | Retained real boundary | | --- | --- | --- | | Callback success/failure helpers and the retry loser | Callback policy, immutable attempt claim, transport acceptance, and settlement | Async retry winner, callback CLI/Gateway concurrency, and fake OpenClaw children | | `install-openclaw` outer CLI | Parser, installer filesystem effects, trust fallback, verification, readiness, and JSON projection | Real fake-OpenClaw, tmux, and Claude executables plus the doctor argv/OS-exit witness | | Lifecycle evidence verifier wrapper | Exact argument policy, bounded reads, private output, redaction, validation codes, and tag round-trip through an injected IO port | Release-script wiring and the retained executable argv/OS-exit witness | | Live tmux opt-in refusal | The same exported two-factor guard called by the executable entry | Live script keeps the guard before discovery/input; doctor retains executable exit projection | | CLI-core import probe | An isolated Worker proves no argv inspection, output, exit, or import side effect | Doctor and runtime-log tests retain real CLI argv, stdout/stderr, OS exit, and environment routing | | Duplicate Claude recovery wrapper | Exact lifecycle identity, CAS, and no-replay settlement through injected terminal/process seams | Codex black-box recovery remains executable and crash/lifecycle suites retain status 86 | | Single auto-approval nested CLI | The real fake-Gateway child imports `parseCliCommand`/`executeCliCommand` for the nested approval | Sequential nested approval remains process-backed; Gateway, monitor, and callback children remain real | `config/public-contract-witnesses.json` records this table as the `static-subprocess-final` old-case → service-invariant → retained-boundary mapping. The product-test count is now 19 of the immutable 48-site baseline (`cli_process` 12, `fake_node_process` 7), or 39.58%, so the static `final_threshold.required` gate is enabled. Store/file-lock competition, crash-86, live monitor PID recovery, callback Gateway, tmux/ps/lsof/Claude adapter, and real Herdr capability probes remain process-backed. The measurement implementation's own CJS/ESM and process-tree probes are reported separately: the baseline has zero diagnostic starts and the current tree has 10 included fake-Node diagnostic starts plus one `other_process_or_adapter` fork. The one canonical diagnostic path is applied to both revisions and is fixed by validation; it cannot be widened to hide a product-test start. ## #126 dynamic subprocess reduction attestation The static call-site count above remains a cheap architecture diagnostic. It is not the final proof for the requirement to remove at least 60% of redundant outer CLI starts: one helper can execute a call site many times, and moving a `spawn` into another helper must not improve the result. Run the final same-machine process-tree attestation from a clean worktree: ```sh node scripts/measure-subprocess-dynamic-evidence.js \ --output /tmp/akk-subprocess-evidence.json ``` This command performs no install and uses no network service. It verifies that `package-lock.json` is byte-identical at current HEAD and the immutable `ea592a88d7af4a709e7a7a1b989dd29e61932935` baseline, creates a detached temporary baseline worktree, links the already-present `node_modules`, builds both revisions, and runs the full tier at concurrency four once per revision. Both full runs must pass; a partial or failed run is not accepted as evidence. `scripts/subprocess-dynamic-hook.cjs` is preloaded before the test workers. It patches every standard `node:child_process` start entry point, synchronizes the patched CommonJS exports into ESM named imports, and propagates the preload and run identity even when a caller supplies an explicit stripped `env`. Every launch also propagates one random, non-secret call ID into the child boot, so synchronous launches are associated directly without scanning PIDs created by other concurrent workers. Nested implementations such as `exec` calling `execFile` emit one record for the real child rather than two wrapper records, and `util.promisify.custom` remains usable. The attestation runs each full tier in one new POSIX process group. The hook records every explicitly detached child as another process-group root. After the direct runner exits, measurement waits on the kernel until the runner group and every recorded detached group are empty; a shell that exits after forking a background descendant therefore cannot create an unobserved quiet gap. Trace files are read twice only after all groups are empty, and any live group fails closed at the configured 30-second timeout. This local attestation consequently supports macOS and Linux and fails closed on Windows. The baseline completion finishes before current measurement starts, and the current completion finishes before either trace is summarized. The final count comes from observed CLI process boots and their process ancestry, not from source locations. Moving a start behind a shared Node helper, shell, or another call expression therefore does not make that start disappear. A targeted CLI start without its corresponding boot, or a CLI boot without an originating test, makes the attestation fail closed. The denominator is the observed full-tree count at the immutable baseline; the 233-call shared `runAgentCli` migration inventory is only one family and is not misreported as the global baseline. Current HEAD must be at most 40% of the same observed baseline (at least a 60% reduction). The report contains only command basenames, CLI actions, option names, counts, status or signal outcomes, and test paths; message bodies, tokens, environment values, and raw argv are never recorded. The reduction gate is paired with retained real-process checks so deleting or reclassifying necessary coverage cannot satisfy it. The current full trace must still demonstrate: - real doctor argv and non-zero OS exit; - a crash-injected CLI exit with status 86; - overlapping CLI processes for Store/terminal lock competition; - a live child PID and a `SIGKILL` monitor-recovery boundary; - real fake-Gateway (`openclaw`) execution; and - real `tmux`, `ps`, `lsof`, and `claude` adapter subprocesses. Each retained observation is tied at runtime to its explicitly scoped, allowlisted `TestContext.name`. The canonical path and exact test name in `config/subprocess-dynamic-evidence.json` are immutable validation inputs, not replaceable substring hints. Multiple argv, exit, signal, or live-PID requirements on one boundary must be satisfied by the same observed CLI process; unrelated children in the same file cannot be combined into a passing witness. Multi-command adapter boundaries likewise require every command beneath one outer CLI process, so separate tests in the same source file cannot be combined into a passing case. Boundaries sharing one canonical test name also share one command group. The retained Claude and terminal-adapter witness is the raw background send case, where one outer CLI process owns every observed `claude`, `tmux`, `ps`, and `lsof` child. The normal fast evidence test validates that immutable configuration and executes a real process-group probe in which an exited shell leaves a delayed 1.5-second background CLI with an explicit stripped environment. A second CJS/ESM matrix covers every sync and async launch API plus promisified `execFile`, exact call-ID deduplication, and stripped-environment propagation; an overlapping sync/concurrent-writer probe prevents shared-trace PID inference from returning. Those probes transparently add ten fake-Node source sites to the separately reported diagnostic scope; one fork probe is classified as `other_process_or_adapter`. They do not increase either revision's product migration numerator, and the same canonical partition is applied to baseline and current. The dynamic full-tree ratio remains the final runtime 60% gate. It validates the already-active preload without recursively starting its probe while the outer dynamic attestation runs, avoiding a measurement of the measurement itself. The expensive two-revision full attestation remains an explicit final/local gate rather than running inside ordinary fast tests. ## #108 performance record The pre-refactor maintainer baseline was 48 files / 683 tests / about 573 seconds. By v0.11.3 it had grown to 48 files / 696 tests; with the two manifest guards added by this work, the comparable before profile was 49 files / 698 tests / 513.96 seconds. The final stable profile is 59 files / 698 tests / 301.05 seconds, a 41.4% reduction with no removed tests. A higher-concurrency passing run reached 240.13 seconds but was not adopted because repeated stress caused resource-starvation false failures. See [the complete #108 performance report](test-performance-issue-108.md) for the before/after critical paths and why the remaining real-process gates make a stable 180-second full suite unattainable without the next handler/provider injection refactor. Issue #120 completed that next handler/provider-injection phase for the three largest lifecycle families. See [the #120 performance report](test-performance-issue-120.md) for the in-process semantic inventory, retained black-box contracts, and final three-run profile.