Compare commits

...
Author SHA1 Message Date
v-openrig-build 8c6c8de86f test: qualify missing-pane repair against historical schema preimages 2026-10-20 07:04:53 +00:00
v-openrig-build 69fc31394b ci: bind command-preservation verification to final PR head 2026-10-20 06:18:21 +00:00
v-openrig-build 92f8f7200b ci: bind final scoped launch checks to current main composition 2026-10-20 06:15:37 +00:00
v-openrig-build 75e65e9eea ci: verify corrected adapter paths and downstream launch composition 2026-10-20 06:13:19 +00:00
v-openrig-build e7e611a776 ci: verify scoped launch environment and configuration preservation 2026-10-20 06:10:12 +00:00
v-openrig-build da0d5d1379 ci: measure stock adapter failure against selected scoped environment policy 2026-10-20 06:06:34 +00:00
v-openrig-build 5f359b888d ci: qualify tmux environment premise on current main 2026-10-20 06:04:19 +00:00
v-openrig-build b69bea1d38 ci: recheck only two attach CLI fixture failures 2026-10-20 06:01:56 +00:00
v-openrig-build 7c3c3d924f ci: complete unrun attach CLI proof and retain explicit exit 2026-10-20 05:59:35 +00:00
v-openrig-build d5c97ba1ab ci: bind attach integration to full source commit 2026-10-20 05:57:02 +00:00
v-openrig-build 5530e824e0 verify: complete previously unrun attach integration checks 2026-10-20 05:50:40 +00:00
v-openrig-build 40a2f05e16 verify: check attach address vertical and affected consumers 2026-10-20 05:47:17 +00:00
v-openrig-build 44470c8554 verify: check core attach address implementation 2026-10-20 05:38:24 +00:00
v-openrig-build 86c7db1a91 verify: measure attach address regressions 2026-10-20 05:35:29 +00:00
v-openrig-build 5696d8034f verify: check final legacy receipt boundary 2026-10-20 05:26:46 +00:00
v-openrig-build d7212df8d5 verify: run affected Slack timing and notice proof 2026-10-20 05:24:35 +00:00
v-openrig-build 18bd7dfaa5 verify: measure Slack timing correction regressions 2026-10-20 05:22:35 +00:00
v-openrig-build a7097d0a38 Verify pod and flat recovery guidance and lifecycle preservation 2026-10-20 05:02:06 +00:00
v-openrig-build 8549be66dd Measure pod partial-up guidance and retry effects before r2 2026-10-20 04:58:26 +00:00
v-openrig-build c0a680e358 Verify the final Slack post path including multipart delivery 2026-10-20 04:52:41 +00:00
v-openrig-build d4a0772489 Verify current-question Slack resolution and unchanged reply paths 2026-10-20 04:50:29 +00:00
v-openrig-build fb318b9b49 Measure selected Slack episode timing regression 2026-10-20 04:47:57 +00:00
v-openrig-build eebce55a73 Verify partial up guidance and preserved launch outcomes 2026-10-20 04:43:03 +00:00
v-openrig-build 5575771ad5 Measure partial up guidance regression before correction 2026-10-20 04:41:20 +00:00
v-openrig-build 917bd021a3 Verify main partial-up retry command options 2026-10-20 04:39:03 +00:00
v-openrig-build 15639a78fa verify: final UID fixtures at the revised head 2026-10-20 04:20:38 +00:00
v-openrig-build 88c84e5849 verify: effective UID evidence and admitted kernel poll boundaries 2026-10-20 04:15:17 +00:00
v-openrig-build a5adc18ac8 verify: corrected Mac owner candidate and preservation cases 2026-10-20 03:52:55 +00:00
v-openrig-build a286675e46 verify: Mac local owner tools and CLI consumer boundaries 2026-10-20 03:48:14 +00:00
v-openrig-build 9d03cac187 test: verify missing-pane unsupported message delta on hosted runner 2026-10-20 03:04:14 +00:00
v-openrig-build 774e08bff4 test: verify corrected baseline fixtures then missing-pane candidate 2026-10-20 02:49:15 +00:00
v-openrig-build f272fcd5b5 test: bind focused red checkout to full commit 2026-10-20 02:44:22 +00:00
v-openrig-build e1e3a6e159 test: run focused missing-pane repair red cases on hosted runner 2026-10-20 02:43:13 +00:00
v-openrig-build acae60990b verify: keep observation controls on the wired delivery guard 2026-10-20 02:03:38 +00:00
v-openrig-build c86a92a8bf verify: isolate remaining nonsole and unreadable pane controls 2026-10-20 02:03:24 +00:00
v-openrig-build 170bc87281 verify: use the repository rig return shape 2026-10-20 01:58:59 +00:00
v-openrig-build 9fb35228a3 verify: include the pinned legacy qualification controller 2026-10-20 01:58:01 +00:00
v-openrig-build 5b791d0a91 verify: qualify legacy pane reconcile with isolated real tmux 2026-10-20 01:57:49 +00:00
v-openrig-build dea37a44b4 verify: recorded wake runtime correction and preservation 2026-10-20 01:12:09 +00:00
v-openrig-build 8235216474 Reproduce the recorded-runtime wake regression before correction 2026-10-20 01:07:26 +00:00
v-openrig-build 4cb11fee0b Verify unsupported wake runtimes without executing a provider 2026-10-20 00:44:12 +00:00
v-openrig-build 6105a8632b Pin the full usage clock candidate revision 2026-10-20 00:34:03 +00:00
v-openrig-build c7508a2e0f Verify the expired usage fixture and shared clock callers 2026-10-20 00:33:50 +00:00
v-openrig-build a0658fb5de Verify readable absence and unavailable-evidence fallback separately 2026-10-19 23:20:21 +00:00
v-openrig-build 604302f29c Observe the chevron-shell fallback against the reviewed source 2026-10-19 23:17:24 +00:00
v-openrig-build 7744a92c79 Verify both missing-proof corrections and affected preservation 2026-10-19 22:26:57 +00:00
v-openrig-build 5f458cd750 Reproduce the non-auto missing-proof case with bypass mode 2026-10-19 22:24:01 +00:00
v-openrig-build fc03d7b652 Observe round-two missing-proof regressions on the reviewed head 2026-10-19 22:20:40 +00:00
v-openrig-build d97bae68d6 Bind the production selector regression fixtures 2026-10-19 22:03:11 +00:00
v-openrig-build 77742e214e Verify scoped fresh handover readiness against base and candidate 2026-10-19 22:02:56 +00:00
v-openrig-build 0f82a8e33d test: bind headerless auto controls to the actual stored launch selection 2026-10-19 21:52:29 +00:00
v-openrig-build 4def9cac73 verify: bind actual returned reader bytes across the bounded readiness matrix 2026-10-19 21:50:42 +00:00
v-openrig-build a391bb3113 test: qualify headerless fresh wrapper with and without existing identity proof 2026-10-19 21:49:17 +00:00
v-openrig-build 077783cad0 verify: measure current-screen primitive against hidden-proof wrapper pair 2026-10-19 21:47:22 +00:00
v-openrig-build ee6c8d99b0 test: qualify current-screen readiness and unavailable-proof working control 2026-10-19 21:47:03 +00:00
v-openrig-build b956de01db test: populate fresh launch state through actual Claude launchHarness 2026-10-19 21:32:31 +00:00
v-openrig-build 0fd48fc927 verify: measure released handover chronology without native process execution 2026-10-19 21:29:37 +00:00
v-openrig-build 9c8a5afc2d test: diagnose predecessor scrollback readiness through fresh handover 2026-10-19 21:29:13 +00:00
v-openrig-build 331f65e135 verify: bind corrected variant assertions and downstream observations 2026-10-19 20:07:54 +00:00
v-openrig-build c005c97ff9 verify: use full candidate commit for Actions checkout 2026-10-19 20:06:43 +00:00
v-openrig-build f57f1bc2e2 verify: compare shared MCP menu repair with merged baseline 2026-10-19 20:06:11 +00:00
v-openrig-build ecf193529e verify: diagnose Claude MCP menus through shared readiness and send paths 2026-10-19 19:39:55 +00:00
v-openrig-build 6d14e88531 verify: measure the selected Codex route correction red and green 2026-10-19 19:29:11 +00:00
v-openrig-build 72834509a1 test: diagnose quoted Codex choices through guarded transport 2026-10-19 15:39:58 +00:00
10 changed files with 1230 additions and 121 deletions

No files matched your search

+55 -121
View File
@@ -1,142 +1,76 @@
name: Tests
# Builds, type-checks and runs every test suite on a clean GitHub runner, and records the file
# list each published package would contain. Each suite is its own job so one failure does not
# hide the others. No provider credentials or operator configuration are supplied.
# Package suites deny external networking; scenario runtime has no external route.
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
on: workflow_dispatch
permissions:
contents: read
concurrency:
group: tests-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-package:
legacy-repair-qualification:
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
path: controller
persist-credentials: false
- uses: actions/checkout@v4
with:
ref: 69b08a17e73916cbdabbf3d01b3aad77a06c48b5
path: source
persist-credentials: false
- uses: actions/checkout@v4
with:
ref: 8250d702a27aa24bb9acf0b4bef12f58fd83df12
path: old046
persist-credentials: false
- uses: actions/checkout@v4
with:
ref: 20991e3cc12166a64fe74ea24a515c3666014844
path: old050
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: source/package-lock.json
- name: Bind inputs and existing hosted tools
run: |
command -v tmux
command -v cc
mkdir -p "$RUNNER_TEMP/legacy-repair"
for tree in source old046 old050 controller; do git -C "$tree" rev-parse HEAD HEAD^{tree} > "$RUNNER_TEMP/legacy-repair/$tree.txt"; done
test "$(git -C source rev-parse HEAD^{tree})" = ea41955f560a1244bb4e3b740248320478f5f8a1
sha256sum controller/diagnostics/*0614.mjs controller/diagnostics/prepare-legacy-schema.mjs > "$RUNNER_TEMP/legacy-repair/controller.sha256"
tmux -V > "$RUNNER_TEMP/legacy-repair/tmux-version.txt"
- run: npm ci
working-directory: source
- run: npm run build
- name: Record the published file list of each package
working-directory: source
- name: Two historical schema and writer preimages through actual repair CLI
shell: bash
env:
SOURCE_PIN: 69b08a17e73916cbdabbf3d01b3aad77a06c48b5
run: |
set -euo pipefail
mkdir -p pack-lists
for p in cli daemon tui; do
(cd packages/$p && npm pack --dry-run --json --ignore-scripts) \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{for(const f of JSON.parse(s)[0].files)console.log(f.path)})' \
| sort > pack-lists/$p.txt
echo "$p: $(wc -l < pack-lists/$p.txt) files"
set +e
set -uo pipefail
export SOURCE_ROOT="$GITHUB_WORKSPACE/source"
status=0
for version in 046 050; do
export LEGACY_VERSION="$version" OLD_ROOT="$GITHUB_WORKSPACE/old$version" PROOF_ROOT="$RUNNER_TEMP/legacy-repair/$version"
mkdir -p "$PROOF_ROOT"
node controller/diagnostics/prepare-legacy-schema.mjs > "$PROOF_ROOT/prepare.stdout" 2> "$PROOF_ROOT/prepare.stderr"
prepare=$?
if [ "$prepare" = 0 ]; then
timeout --signal=TERM --kill-after=10s 180s node controller/diagnostics/legacy-repair-0614.mjs > "$PROOF_ROOT/stdout" 2> "$PROOF_ROOT/stderr"
result=$?
else result=99; fi
printf 'prepare=%s\nfixture=%s\n' "$prepare" "$result" > "$PROOF_ROOT/exits.txt"
if [ "$prepare" != 0 ] || [ "$result" != 0 ]; then status=1; fi
done
- uses: actions/upload-artifact@v4
with:
name: pack-lists
path: pack-lists/
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
repo-checks:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:repo
package-tests:
if: ${{ github.event_name != 'push' }}
# macOS, the platform OpenRig runs on: several daemon and cli tests read macOS ps columns,
# file-inode behaviour and release tags.
runs-on: macos-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
package: [daemon, cli, tui, ui]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: brew install tmux
- run: npm ci
- name: Build the packages the tests import
run: npm run build
- name: Run the suite without ambient credentials or external network
run: |
suite_root=$(mktemp -d "$RUNNER_TEMP/opr-test.XXXXXX")
suite_root=$(cd "$suite_root" && pwd -P)
mkdir -p "$suite_root/home" "$suite_root/bin"
# Same system bytes, nonprivileged mode; no system file is changed.
/usr/bin/install -m 0755 /bin/ps "$suite_root/bin/ps"
# Only this job's temporary sockets (including tmux) are reachable.
env -i HOME="$suite_root/home" PATH="$PATH" TMPDIR="$suite_root" TMUX_TMPDIR="$suite_root" \
CI=true TERM=xterm-256color \
sandbox-exec -D IPC_ROOT="$suite_root" -p '(version 1)(allow default)(deny network-outbound)(allow network-outbound (remote ip "localhost:*"))(allow network-outbound (subpath (param "IPC_ROOT")))' \
/bin/bash -s "${{ matrix.package }}" <<'SUITE'
set -euo pipefail
node scripts/ci-macos-process-probe.mjs
# The production readers use bare ps. Select real system bytes only after
# self/owned-child observation and network/IPC confinement are proved.
export PATH="$TMPDIR/bin:$PATH"
test "$(command -v ps)" = "$TMPDIR/bin/ps"
exec npm run test -w "packages/$1"
SUITE
installed-scenario:
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Installed package, daemon restart, and seeded durability failure
run: bash scripts/run-pr-scenarios.sh
git -C source diff --exit-code > "$RUNNER_TEMP/legacy-repair/source-unchanged.txt"
exit "$status"
- uses: actions/upload-artifact@v4
if: always()
with:
name: installed-scenario-evidence
path: dist/pr-scenarios/
name: legacy-repair-0614
path: ${{ runner.temp }}/legacy-repair/
if-no-files-found: error
+35
View File
@@ -0,0 +1,35 @@
import fs from 'node:fs';
import path from 'node:path';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {pathToFileURL} from 'node:url';
const source=path.resolve(process.env.SOURCE_ROOT),out=path.resolve(process.env.PROOF_ROOT);
const home=path.join(out,'controls-home');fs.mkdirSync(home,{recursive:true});
for(const k of Object.keys(process.env))if(/^(OPENRIG_|RIGGED_|CLAUDE_|CODEX_|ANTHROPIC_|OPENAI_|TMUX)/.test(k))delete process.env[k];
Object.assign(process.env,{HOME:home,OPENRIG_HOME:path.join(home,'.openrig'),CODEX_HOME:path.join(home,'.codex')});
const req=createRequire(path.join(source,'package.json')),Database=req('better-sqlite3');
const load=rel=>import(pathToFileURL(path.join(source,'packages/daemon/dist',rel+'.js')));
const {migrate}=await load('db/migrate'),{ALL_MIGRATIONS}=await load('db/all-migrations');
const {RigRepository}=await load('domain/rig-repository'),{SessionRegistry}=await load('domain/session-registry');
const {DiscoveryRepository}=await load('domain/discovery-repository'),{EventBus}=await load('domain/event-bus');
const {ClaimService}=await load('domain/claim-service');
const {SeatDeliveryGuard,resolveGuardTarget}=await load('domain/seat-delivery-guard');
const records=[];
for(const mode of ['nonsole','unobservable']){
const db=new Database(':memory:');migrate(db,ALL_MIGRATIONS);
try{
const rigRepo=new RigRepository(db),sessionRegistry=new SessionRegistry(db),eventBus=new EventBus(db);
const rig=rigRepo.createRig('private-controls'),node=rigRepo.addNode(rig.id,'worker',{runtime:'claude-code',podId:null});
const seat='worker@private-controls';sessionRegistry.registerSession(node.id,seat);sessionRegistry.updateBinding(node.id,{tmuxSession:seat});
const calls=[];const tmux={hasSession:async()=>true,listPanes:async()=>{if(mode==='unobservable')throw Error('fixture observation unavailable');return[{id:'%1'},{id:'%2'}];},getPaneCommand:async()=> 'claude',setSessionOption:async(...args)=>{calls.push(['metadata',args]);return{ok:true};},sendText:async()=>{throw Error('forbidden input');},sendKeys:async()=>{throw Error('forbidden input');},createSession:async()=>{throw Error('forbidden launch');}};
tmux.deliveryGuard=new SeatDeliveryGuard(db,name=>resolveGuardTarget(db,name));
const svc=new ClaimService({db,rigRepo,sessionRegistry,eventBus,discoveryRepo:new DiscoveryRepository(db),tmuxAdapter:tmux,transcriptStore:{enabled:false}});
const result=await svc.reconcileSession({sessionName:seat});
const binding=sessionRegistry.getBindingForNode(node.id),sessions=db.prepare('SELECT * FROM sessions WHERE node_id=? ORDER BY id').all(node.id);
const identity=db.prepare('SELECT * FROM seat_identity_verdicts WHERE node_id=?').get(node.id);
assert.equal(result.ok,true);assert.equal(binding.tmuxPane,null);assert(result.result.projectionDrift.some(x=>x.includes(mode==='nonsole'?'2 panes':'fixture observation unavailable')));
records.push({mode,label:'synthetic pane observation seam; real SQLite/ClaimService; no CLI or real tmux control',result,binding,sessions,identity,calls,sendabilityNotRestored:true});
}finally{db.close();}
}
fs.writeFileSync(path.join(out,'CONTROLS.json'),JSON.stringify(records,null,2));
console.log('two remaining observation controls completed; HTTP/ok is not pane-repair proof');
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { exec, execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const ef=promisify(execFile), ex=promisify(exec);
const source=path.resolve(process.env.SOURCE_ROOT), out=path.resolve(process.env.PROOF_ROOT);
const home=path.join(out,'scratch-home'), socket=path.join(out,'fixture.sock');
fs.mkdirSync(home,{recursive:true});
// This process and every CLI child receive only the scratch homes and owned endpoint.
for(const key of Object.keys(process.env)) if(/^(OPENRIG_|RIGGED_|CLAUDE_|CODEX_|ANTHROPIC_|OPENAI_|TMUX)/.test(key)) delete process.env[key];
Object.assign(process.env,{HOME:home,OPENRIG_HOME:path.join(home,'.openrig'),XDG_CONFIG_HOME:path.join(home,'.config'),CODEX_HOME:path.join(home,'.codex')});
const bin=path.join(home,'bin');fs.mkdirSync(bin,{recursive:true});
process.env.PATH=bin+':'+process.env.PATH;
fs.writeFileSync(path.join(bin,'codex'),'#!/bin/sh\ncase "$1" in --version) echo "codex-cli 0.139.0";; --help) echo "Usage: codex [OPTIONS] [PROMPT]"; echo "--no-daemon";; esac\n',{mode:0o755});
// Inert stdin recorder; its executable name/argv can be read by the unmodified runtime observer.
fs.writeFileSync(path.join(out,'recorder.c'),`#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\nint main(int argc,char **argv){if(argc>1&&!strcmp(argv[1],"--version")){puts("2.1.0 (Claude Code)");return 0;}if(argc>1&&!strcmp(argv[1],"--help")){puts("Usage: claude [options] [prompt]");return 0;}setvbuf(stdout,NULL,_IONBF,0);puts("FIXTURE_RECORDER_READY");FILE *f=fopen(getenv("FIXTURE_RECORD"),"ab");if(!f)return 2;char b[4096];while(fgets(b,sizeof b,stdin)){fputs(b,f);fflush(f);printf("RECORDER_READ:%s",b);}fclose(f);return 0;}\n`);
await ef('cc',['-O0',path.join(out,'recorder.c'),'-o',path.join(bin,'claude')]);
const q=s=>"'"+s.replaceAll("'","'\\''")+"'";
const records={identity:{label:'public-cut source-built CLI/createDaemon; inert recorder, not sealed archive/native provider',source:process.env.SOURCE_PIN},commands:[],cli:[],checks:{}};
const save=()=>fs.writeFileSync(path.join(out,'RESULT.json'),JSON.stringify(records,null,2));
let phase='startup',daemon,server;
const tmuxExec=async command=>{assert.match(command,/^tmux\b/);records.commands.push({phase,command});return (await ex(command.replace(/^tmux\b/,'tmux -S '+q(socket)),{env:process.env,timeout:10000,maxBuffer:4*1024*1024})).stdout;};
const tmux=async(...args)=>(await ef('tmux',['-S',socket,...args],{env:process.env,timeout:10000})).stdout;
const req=createRequire(path.join(source,'package.json'));
const {serve}=await import(pathToFileURL(req.resolve('@hono/node-server')));
const {createDaemon}=await import(pathToFileURL(path.join(source,'packages/daemon/dist/startup.js')));
const {HistoryQuery}=await import(pathToFileURL(path.join(source,'packages/daemon/dist/domain/history-query.js')));
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const token='00000000-0000-4000-8000-000000000001';
const cli=async args=>{const startedAt=new Date().toISOString();let r;try{const x=await ef(process.execPath,[path.join(source,'packages/cli/dist/index.js'),...args],{env:process.env,timeout:25000,maxBuffer:4*1024*1024});r={code:0,...x};}catch(e){r={code:typeof e.code==='number'?e.code:1,stdout:e.stdout??'',stderr:e.stderr??'',error:e.message};}records.cli.push({phase,args,startedAt,endedAt:new Date().toISOString(),...r});save();return r;};
const data=r=>{try{return JSON.parse(r.stdout);}catch{return null;}};
try{
daemon=await createDaemon({dbPath:path.join(home,'fixture.sqlite'),tmuxExec,terminalBearerToken:null,bearerToken:null,webUiEnabled:false});
server=serve({fetch:daemon.app.fetch,hostname:'127.0.0.1',port:0});
await new Promise(r=>server.listening?r():server.once('listening',r));
const port=server.address().port;process.env.OPENRIG_URL='http://127.0.0.1:'+port;
records.identity.endpoint=process.env.OPENRIG_URL;records.identity.socket=socket;records.identity.home=home;
const {db,deps}=daemon,{rigRepo,sessionRegistry,transcriptStore,queueRepo}=deps;
const rig=rigRepo.createRig('pane-fixture');
async function seat(logical,nullable){const name=logical.replaceAll('.','-')+'@pane-fixture',record=path.join(out,logical+'.stdin');
await tmux('new-session','-d','-s',name,'-x','120','-y','35',`FIXTURE_RECORD=${q(record)} exec ${q(path.join(bin,'claude'))} --resume ${token}`);
let state='';for(let n=0;n<50;n++){state=await tmux('capture-pane','-p','-t',name);if(state.includes('FIXTURE_RECORDER_READY'))break;await sleep(40);}assert.match(state,/FIXTURE_RECORDER_READY/);
const node=rigRepo.addNode(rig.id,logical,{runtime:'claude-code',cwd:home,podId:null});
const session=sessionRegistry.registerSession(node.id,name);sessionRegistry.updateStatus(session.id,'running');sessionRegistry.updateStartupStatus(session.id,'ready',new Date().toISOString());
const pane=(await tmux('display-message','-p','-t',name,'#{pane_id}')).trim();
sessionRegistry.updateBinding(node.id,{tmuxSession:name,...(nullable?{}:{tmuxPane:pane})});
// Preserve the reported runtime/resume-type anomaly; never normalize it in this fixture.
sessionRegistry.updateResumeToken(session.id,'codex_id',token,'hook');
return {name,node,session,pane,record};}
const target=await seat('legacy.fixture',true),control=await seat('control.fixture',false);
const transcriptPath=transcriptStore.getTranscriptPath(rig.name,target.name);
transcriptStore.ensureTranscriptDir(rig.name);fs.writeFileSync(transcriptPath,'HISTORY_SENTINEL before reconcile\n--- SESSION BOUNDARY: retained fixture marker ---\nHISTORY_SENTINEL prior boundary\n');
const history=new HistoryQuery({transcriptsRoot:path.dirname(path.dirname(transcriptPath)),exec:async()=>{throw Error('native history backend not selected');}});
const obligation=await queueRepo.create({qitemId:'qitem-owned-legacy-qualification',sourceSession:control.name,destinationSession:target.name,body:'Owned fixture obligation survives reconcile',nudge:false});
const facts=async()=>({binding:db.prepare('SELECT * FROM bindings WHERE node_id=?').get(target.node.id),sessions:db.prepare('SELECT * FROM sessions WHERE node_id=? ORDER BY id').all(target.node.id),tenures:db.prepare('SELECT * FROM occupant_tenures WHERE node_id=? ORDER BY generation_ordinal').all(target.node.id),node:db.prepare('SELECT * FROM nodes WHERE id=?').get(target.node.id),queue:db.prepare('SELECT * FROM queue_items WHERE qitem_id=?').get(obligation.qitemId),tmux:(await tmux('display-message','-p','-t',target.name,'#{pid}|#{pane_id}|#{pane_pid}|#{session_id}')).trim(),pane:await tmux('capture-pane','-p','-t',target.name),stdin:fs.existsSync(target.record)?fs.readFileSync(target.record,'utf8'):'',transcript:fs.readFileSync(transcriptPath,'utf8'),history:await history.searchSeat(rig.name,target.name,'HISTORY_SENTINEL'),transcriptPath});
phase='matching-control';const matching=await cli(['send',control.name,'MATCHING_BINDING_CONTROL','--json']);assert.equal(matching.code,0);assert.equal(data(matching)?.ok,true);
phase='before-refusal';const initial=await facts();records.before=initial;save();const refused=await cli(['send',target.name,'MUST_NOT_REACH_NULL_BINDING','--json']);assert.notEqual(refused.code,0);assert.match(refused.stdout+refused.stderr,/Managed pane identity unavailable or changed/);assert.equal(fs.readFileSync(target.record,'utf8'),initial.stdin);
phase='reconcile';const beforeReconcile=await facts();records.beforeReconcile=beforeReconcile;const seq=db.prepare('SELECT max(seq) AS seq FROM events').get().seq;
const result=await cli(['reconcile-session',target.name,'--no-launch','--json']);records.reconcileBody=data(result);records.afterReconcile=await facts();records.reconcileEvents=db.prepare('SELECT * FROM events WHERE seq>? ORDER BY seq').all(seq);save();
assert.equal(result.code,0);assert.equal(records.reconcileBody?.ok,true);assert.equal(records.reconcileBody.result.continuity,'unverified');
assert.equal(records.afterReconcile.binding.tmux_pane,target.pane);assert.equal(records.afterReconcile.tmux,beforeReconcile.tmux);assert.equal(records.afterReconcile.stdin,beforeReconcile.stdin);assert.equal(records.afterReconcile.pane,beforeReconcile.pane);
const forbidden=records.commands.filter(x=>x.phase==='reconcile'&&/\b(?:send-keys|paste-buffer|load-buffer|new-session|respawn-pane|kill-session|kill-pane)\b/.test(x.command));records.checks.reconcileForbiddenCommands=forbidden;assert.equal(forbidden.length,0);
phase='after-send';const sent=await cli(['send',target.name,'AFTER_RECONCILE_MARKER','--json']);assert.equal(sent.code,0);assert.equal(data(sent)?.ok,true);
for(let n=0;n<40;n++){if(fs.readFileSync(target.record,'utf8').includes('AFTER_RECONCILE_MARKER'))break;await sleep(50);}records.afterSend=await facts();assert.match(records.afterSend.stdin,/AFTER_RECONCILE_MARKER/);assert.match(records.afterSend.pane,/AFTER_RECONCILE_MARKER/);
records.checks.queueBytesPreserved=JSON.stringify(initial.queue)===JSON.stringify(records.afterReconcile.queue);assert.equal(records.checks.queueBytesPreserved,true);
records.checks.historyMarkersPreserved=records.afterReconcile.transcript.includes('HISTORY_SENTINEL before reconcile')&&records.afterReconcile.transcript.includes('retained fixture marker');
records.checks.oldResumeAnomalyPreserved=records.afterReconcile.sessions.find(s=>s.id===target.session.id).resume_type==='codex_id'&&records.afterReconcile.node.runtime==='claude-code';assert.equal(records.checks.oldResumeAnomalyPreserved,true);
records.checks.sessionRowsAdded=records.afterReconcile.sessions.length-initial.sessions.length;records.checks.tenuresAdded=records.afterReconcile.tenures.length-initial.tenures.length;
records.status='completed';await db.backup(path.join(out,'fixture-after.sqlite'));save();
}catch(error){records.status='failed';records.failure={message:error.message,stack:error.stack,phase};save();process.exitCode=1;}
finally{phase='owned-shutdown';try{await tmux('kill-server');records.ownedTmuxShutdown=true;}catch(e){records.ownedTmuxShutdown={error:e.message};}try{if(server)await new Promise(r=>server.close(r));}catch{}try{daemon?.db.close();}catch{}save();}
process.exit(process.exitCode??0);
+114
View File
@@ -0,0 +1,114 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { exec, execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const ef=promisify(execFile), ex=promisify(exec);
const source=path.resolve(process.env.SOURCE_ROOT), out=path.resolve(process.env.PROOF_ROOT);
const version=process.env.LEGACY_VERSION;
const home=path.join(out,'scratch-home'), socket=path.join(out,'fixture.sock');
fs.mkdirSync(home,{recursive:true});
// This process and every CLI child receive only the scratch homes and owned endpoint.
for(const key of Object.keys(process.env)) if(/^(OPENRIG_|RIGGED_|CLAUDE_|CODEX_|ANTHROPIC_|OPENAI_|TMUX)/.test(key)) delete process.env[key];
Object.assign(process.env,{HOME:home,OPENRIG_HOME:path.join(home,'.openrig'),XDG_CONFIG_HOME:path.join(home,'.config'),CODEX_HOME:path.join(home,'.codex')});
const bin=path.join(home,'bin');fs.mkdirSync(bin,{recursive:true});
process.env.PATH=bin+':'+process.env.PATH;
fs.writeFileSync(path.join(bin,'codex'),'#!/bin/sh\ncase "$1" in --version) echo "codex-cli 0.139.0";; --help) echo "Usage: codex [OPTIONS] [PROMPT]"; echo "--no-daemon";; esac\n',{mode:0o755});
// Inert stdin recorder; its executable name/argv can be read by the unmodified runtime observer.
fs.writeFileSync(path.join(out,'recorder.c'),`#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\nint main(int argc,char **argv){if(argc>1&&!strcmp(argv[1],"--version")){puts("2.1.0 (Claude Code)");return 0;}if(argc>1&&!strcmp(argv[1],"--help")){puts("Usage: claude [options] [prompt]");return 0;}setvbuf(stdout,NULL,_IONBF,0);puts("FIXTURE_RECORDER_READY");FILE *f=fopen(getenv("FIXTURE_RECORD"),"ab");if(!f)return 2;char b[4096];while(fgets(b,sizeof b,stdin)){fputs(b,f);fflush(f);printf("RECORDER_READ:%s",b);}fclose(f);return 0;}\n`);
await ef('cc',['-O0',path.join(out,'recorder.c'),'-o',path.join(bin,'claude')]);
const q=s=>"'"+s.replaceAll("'","'\\''")+"'";
const records={identity:{label:'historical schema/writers; synthetic records and inert native recorder; source-built product-tree equivalent, not sealed installed archive',version,source:process.env.SOURCE_PIN},commands:[],cli:[],checks:{}};
const save=()=>fs.writeFileSync(path.join(out,'RESULT.json'),JSON.stringify(records,null,2));
let phase='startup',daemon,server;
const tmuxExec=async command=>{assert.match(command,/^tmux\b/);records.commands.push({phase,command});return (await ex(command.replace(/^tmux\b/,'tmux -S '+q(socket)),{env:process.env,timeout:10000,maxBuffer:4*1024*1024})).stdout;};
const tmux=async(...args)=>(await ef('tmux',['-S',socket,...args],{env:process.env,timeout:10000})).stdout;
const req=createRequire(path.join(source,'package.json'));
const {serve}=await import(pathToFileURL(req.resolve('@hono/node-server')));
const {createDaemon}=await import(pathToFileURL(path.join(source,'packages/daemon/dist/startup.js')));
const {HistoryQuery}=await import(pathToFileURL(path.join(source,'packages/daemon/dist/domain/history-query.js')));
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const token='00000000-0000-4000-8000-000000000001';
const cli=async args=>{const startedAt=new Date().toISOString();let r;try{const x=await ef(process.execPath,[path.join(source,'packages/cli/dist/index.js'),...args],{env:process.env,timeout:25000,maxBuffer:4*1024*1024});r={code:0,...x};}catch(e){r={code:typeof e.code==='number'?e.code:1,stdout:e.stdout??'',stderr:e.stderr??'',error:e.message};}records.cli.push({phase,args,startedAt,endedAt:new Date().toISOString(),...r});save();return r;};
const data=r=>{try{return JSON.parse(r.stdout);}catch{return null;}};
try {
const old=req(path.join(source,`.l1-old-${version}.cjs`));
const dbPath=path.join(home,'fixture.sqlite'), historical=old.createDb(dbPath);old.migrateOld(historical);
const rigs=new old.RigRepository(historical), sessions=new old.SessionRegistry(historical);
const rig=rigs.createRig('pane-fixture'), seats={};
for(const logical of ['legacy.fixture','control.fixture','ambiguous.fixture','protected.fixture','missing.fixture']) {
const name=logical.replaceAll('.','-')+'@pane-fixture',record=path.join(out,logical+'.stdin');
await tmux('new-session','-d','-s',name,'-x','120','-y','35',`FIXTURE_RECORD=${q(record)} exec ${q(path.join(bin,'claude'))} --resume ${token}`);
let screen='';for(let n=0;n<50;n++){screen=await tmux('capture-pane','-p','-t',name);if(screen.includes('FIXTURE_RECORDER_READY'))break;await sleep(40);}assert.match(screen,/FIXTURE_RECORDER_READY/);
const node=rigs.addNode(rig.id,logical,{runtime:'claude-code',cwd:home,podId:null});
// These are the historical repository writers used by the historical launcher.
// No old launcher/provider is executed: owned inert tmux was created above.
const session=sessions.registerSession(node.id,name);sessions.updateStatus(session.id,'running');sessions.updateStartupStatus(session.id,'ready',new Date().toISOString());
sessions.updateBinding(node.id,{tmuxSession:name});sessions.updateResumeToken(session.id,'codex_id',token,'hook');
historical.prepare("UPDATE bindings SET updated_at='2000-01-01' WHERE node_id=?").run(node.id);
if(version==='050')historical.prepare("UPDATE sessions SET restore_policy='relaunch_fresh' WHERE id=?").run(session.id);
const pane=(await tmux('display-message','-p','-t',name,'#{pane_id}')).trim();
assert.equal(historical.prepare('SELECT tmux_pane FROM bindings WHERE node_id=?').get(node.id).tmux_pane,null);
seats[logical]={name,node,session,pane,record};
}
records.historical={migrations:historical.prepare('SELECT * FROM schema_migrations ORDER BY name').all(),sessions:historical.prepare('SELECT * FROM sessions ORDER BY id').all(),bindings:historical.prepare('SELECT * FROM bindings ORDER BY node_id').all()};
await historical.backup(path.join(out,'historical-preimage.sqlite'));historical.close();save();
daemon=await createDaemon({dbPath,tmuxExec,terminalBearerToken:null,bearerToken:null,webUiEnabled:false});
server=serve({fetch:daemon.app.fetch,hostname:'127.0.0.1',port:0});await new Promise(r=>server.listening?r():server.once('listening',r));
process.env.OPENRIG_URL='http://127.0.0.1:'+server.address().port;
const {db,deps}=daemon, {sessionTransport,transcriptStore}=deps, guard=sessionTransport.deliveryGuard;
assert.ok(guard);records.identity.endpoint=process.env.OPENRIG_URL;records.identity.socket=socket;
const target=seats['legacy.fixture'],control=seats['control.fixture'];
const transcriptPath=transcriptStore.getTranscriptPath(rig.name,target.name);transcriptStore.ensureTranscriptDir(rig.name);
fs.appendFileSync(transcriptPath,'\nHISTORY_SENTINEL retained legacy bytes\n');
await sleep(100);
const stored=()=>Object.fromEntries(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name").all().map(({name})=>[name,db.prepare('SELECT * FROM "'+name+'"').all()]));
const native=async()=>({identity:(await tmux('display-message','-p','-t',target.name,'#{pid}|#{pane_id}|#{pane_pid}|#{session_id}')).trim(),stdin:fs.readFileSync(target.record,'utf8'),transcript:fs.readFileSync(transcriptPath,'utf8')});
const repair=seat=>cli(['reconcile-session',seat.name,'--repair-missing-pane','--no-launch','--json']);
// Matching binding positive control: sending is possible to this owned recorder.
db.prepare('UPDATE bindings SET tmux_pane=? WHERE node_id=?').run(control.pane,control.node.id);
phase='matching-send-control';const matching=await cli(['send',control.name,'MATCHING_BINDING_CONTROL','--json']);assert.equal(matching.code,0);assert.equal(data(matching)?.ok,true);
for(let n=0;n<40&&!fs.readFileSync(control.record,'utf8').includes('MATCHING_BINDING_CONTROL');n++)await sleep(25);
assert.match(fs.readFileSync(control.record,'utf8'),/MATCHING_BINDING_CONTROL/);
phase='before-null-refusal';const refused=await cli(['send',target.name,'MUST_NOT_REACH_NULL_BINDING','--json']);assert.notEqual(refused.code,0);assert.match(refused.stdout+refused.stderr,/Managed pane identity unavailable or changed/);assert.equal(fs.readFileSync(target.record,'utf8'),'');
// Freeze a real retained outbox record with the NULL-pane identity.
phase='freeze-retained';await guard.set(target.node.id,{mode:'hold'},'fixture','owned preservation control');
const held=await sessionTransport.send(target.name,'STALE_NULL_SNAPSHOT',{deliveryId:'owned-retained-before-repair',actorSession:control.name});assert.equal(held.sent,false);assert.equal(held.outcome,'retained');
const heldBefore=db.prepare('SELECT * FROM outbox_entries WHERE outbox_id=?').get('owned-retained-before-repair');
assert.equal(JSON.parse(heldBefore.guard_binding).pane,null);await guard.set(target.node.id,{mode:'off'},'fixture','owned repair phase');
// Observe production adapter calls without substituting their implementation.
const adapter=deps.tmuxAdapter,counts={};assert.ok(adapter);
for(const key of ['probeSession','listSessions','listPanes','getPanePid']){const original=adapter[key].bind(adapter);adapter[key]=async(...args)=>{counts[key]=(counts[key]??0)+1;const result=await original(...args);records.observations??=[];records.observations.push({phase,key,args,result});return result;};}
phase='repair';const before=stored(),nativeBefore=await native();records.before=before;records.nativeBefore=nativeBefore;save();
const result=await repair(target),body=data(result);records.repair={result,body,counts:{...counts}};
const after=stored(),nativeAfter=await native();records.after=after;records.nativeAfter=nativeAfter;save();
assert.equal(result.code,0);assert.equal(body?.result?.mode,'repair-missing-pane');assert.equal(body.result.sessionId,target.session.id);assert.equal(body.result.continuity,'unverified');
assert.deepEqual(counts,{probeSession:2,listSessions:2,listPanes:2,getPanePid:2});
const bindingBefore=before.bindings.find(b=>b.node_id===target.node.id),bindingAfter=after.bindings.find(b=>b.node_id===target.node.id);
assert.equal(bindingAfter.tmux_pane,target.pane);assert.notEqual(bindingAfter.updated_at,bindingBefore.updated_at);
assert.deepEqual({...bindingAfter,tmux_pane:bindingBefore.tmux_pane,updated_at:bindingBefore.updated_at},bindingBefore);
assert.deepEqual({...after,bindings:before.bindings},before);assert.deepEqual(nativeAfter,nativeBefore);
const forbidden=records.commands.filter(x=>x.phase==='repair'&&/\b(?:send-keys|paste-buffer|load-buffer|new-session|respawn-pane|kill-session|kill-pane|pipe-pane)\b/.test(x.command));assert.deepEqual(forbidden,[]);
records.checks.nullOnlyAllTablesPreserved=true;records.checks.sameNativePidHistoryInput=true;records.checks.twoMatchingObservations=true;
records.checks.legacyAbsentTenure={count:after.occupant_tenures.filter(t=>t.node_id===target.node.id).length,accepted:true};assert.equal(records.checks.legacyAbsentTenure.count,0);
assert.equal(after.sessions.find(s=>s.id===target.session.id).restore_policy,version==='050'?'relaunch_fresh':'resume_if_possible');
// No automatic retarget or delivery; retries keep the real stored binding bytes.
phase='stale-snapshot';const stale=await sessionTransport.send(target.name,'STALE_NULL_SNAPSHOT',{committedOutboxIds:['owned-retained-before-repair'],actorSession:control.name});
assert.equal(stale.ok,false);assert.equal(stale.reason,'guard_target_changed');
const retry=await sessionTransport.send(target.name,'STALE_NULL_SNAPSHOT',{deliveryId:'owned-retained-before-repair',actorSession:control.name});assert.equal(retry.sent,false);assert.equal(retry.outcome,'retained');
assert.deepEqual(db.prepare('SELECT * FROM outbox_entries WHERE outbox_id=?').get('owned-retained-before-repair'),heldBefore);assert.equal(fs.readFileSync(target.record,'utf8'),nativeBefore.stdin);records.stale={stale,retry};
// Real ambiguous topology, protected seat, and missing-current-session controls.
records.refusals=[];
const negative=async(label,seat)=>{phase=label;const before=stored(),r=await repair(seat),after=stored();records.refusals.push({label,result:r,body:data(r)});save();assert.notEqual(r.code,0);assert.equal(data(r)?.ok,false);assert.deepEqual(after,before);assert.equal(fs.readFileSync(seat.record,'utf8'),'');};
await tmux('split-window','-d','-t',seats['ambiguous.fixture'].name,'sleep 120');await negative('ambiguous-pane',seats['ambiguous.fixture']);
await guard.set(seats['protected.fixture'].node.id,{mode:'hold'},'fixture','preserve person protection');await negative('protected',seats['protected.fixture']);
db.prepare('DELETE FROM sessions WHERE node_id=?').run(seats['missing.fixture'].node.id);await negative('missing-current-session',seats['missing.fixture']);
phase='after-send';const sent=await cli(['send',target.name,'AFTER_REPAIR_MARKER','--json']);records.afterSend=sent;assert.equal(sent.code,0);assert.equal(data(sent)?.ok,true);
for(let n=0;n<40&&!fs.readFileSync(target.record,'utf8').includes('AFTER_REPAIR_MARKER');n++)await sleep(25);
assert.match(fs.readFileSync(target.record,'utf8'),/AFTER_REPAIR_MARKER/);assert.doesNotMatch(fs.readFileSync(target.record,'utf8'),/STALE_NULL_SNAPSHOT|MUST_NOT_REACH_NULL_BINDING/);
records.checks.afterRepairNewSendWorks=true;records.status='completed';await db.backup(path.join(out,'fixture-after.sqlite'));save();
} catch(error) {records.status='failed';records.failure={message:error.message,stack:error.stack,phase};save();process.exitCode=1;}
finally {phase='owned-shutdown';try{await tmux('kill-server');records.ownedTmuxShutdown=true;}catch(e){records.ownedTmuxShutdown={error:e.message};}try{if(server)await new Promise(r=>server.close(r));}catch{}try{daemon?.db.close();}catch{}save();}
process.exit(process.exitCode??0);
+20
View File
@@ -0,0 +1,20 @@
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import crypto from 'node:crypto';
const source=path.resolve(process.env.SOURCE_ROOT), old=path.resolve(process.env.OLD_ROOT), out=path.resolve(process.env.PROOF_ROOT);
const require=createRequire(path.join(source,'package.json'));
const {build}=require('esbuild');
const startup=fs.readFileSync(path.join(old,'packages/daemon/src/startup.ts'),'utf8');
const list=startup.match(/migrate\(db, \[([^\]]+)\]\)/)[1];
const imports=[...startup.matchAll(/^import \{ ([^}]+) \} from "(\.\/db\/migrations\/[^" ]+)";/gm)];
const chosen=imports.filter(m=>list.split(',').map(s=>s.trim()).includes(m[1]));
if(chosen.length!==list.split(',').length)throw Error('Incomplete historical migration binding');
const base=path.join(old,'packages/daemon/src');
const entry=chosen.map(m=>`import {${m[1]}} from ${JSON.stringify(path.join(base,m[2].replace(/\.js$/,'.ts')))};`).join('\n')+
`\nimport {migrate} from ${JSON.stringify(path.join(base,'db/migrate.ts'))};\nexport {createDb} from ${JSON.stringify(path.join(base,'db/connection.ts'))};\nexport {RigRepository} from ${JSON.stringify(path.join(base,'domain/rig-repository.ts'))};\nexport {SessionRegistry} from ${JSON.stringify(path.join(base,'domain/session-registry.ts'))};\nexport const migrateOld=db=>migrate(db,[${list}]);`;
const entryPath=path.join(out,'historical-entry.ts');fs.writeFileSync(entryPath,entry);
await build({entryPoints:[entryPath],bundle:true,platform:'node',format:'cjs',packages:'external',outfile:path.join(source,`.l1-old-${process.env.LEGACY_VERSION}.cjs`),metafile:true}).then(r=>{
const files=Object.keys(r.metafile.inputs).sort().map(f=>({path:path.relative(old,path.resolve(f)),sha256:crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex')}));
fs.writeFileSync(path.join(out,'historical-source-binding.json'),JSON.stringify({migrationCount:chosen.length,files},null,2));
});
@@ -0,0 +1,150 @@
import { describe, expect, it, vi } from "vitest";
import { ClaudeCodeAdapter } from "../src/adapters/claude-code-adapter.js";
import type { NodeBinding, RuntimeAdapter } from "../src/domain/runtime-adapter.js";
import type { NativeProcessRow } from "../src/domain/native-process-lineage.js";
import type { TmuxAdapter } from "../src/adapters/tmux.js";
import { CLAUDE_BYPASS_CONSENT } from "./fixtures/claude-bypass-consent.js";
const token = "22222222-2222-4222-8222-222222222222";
const history = "Claude Code v2.1.303\n❯ predecessor finished turn\n ⏵⏵ accept edits on (shift+tab to cycle)";
const binding: NodeBinding = { id: "binding", nodeId: "node", tmuxSession: "seat", tmuxPane: "%9",
tmuxWindow: null, cmuxWorkspace: null, cmuxSurface: null, updatedAt: "", cwd: "/fixture", launchGeneration: 7 };
function fixture() {
const capturePaneScreen = vi.fn(async (): Promise<string | null> => "operator@host$ ");
const capturePaneContent = vi.fn(async () => history);
const getPaneCommand = vi.fn(async () => "zsh");
const getPanePid = vi.fn(async (): Promise<number | null> => 100);
const listProcesses = vi.fn((): NativeProcessRow[] => { throw new Error("ps unavailable"); });
const tmux = { hasSession: vi.fn(async () => true), getPaneCommand, capturePaneContent, capturePaneScreen,
sendText: vi.fn(async () => ({ ok: true })), sendKeys: vi.fn(async () => ({ ok: true })),
listPanes: vi.fn(async () => [{ id: "%9" }]), getPanePid } as unknown as TmuxAdapter;
const fail = () => { throw new Error("unexpected filesystem access"); };
const adapter = new ClaudeCodeAdapter({ tmux, listProcesses, sessionIdFactory: () => token, sleep: async () => {},
fsOps: { exists: () => false, readFile: fail, writeFile: fail, mkdirp: fail, copyFile: fail } });
return { adapter, capturePaneScreen, capturePaneContent, getPaneCommand, getPanePid, listProcesses };
}
const launch = (adapter: ClaudeCodeAdapter, opts: Partial<Parameters<RuntimeAdapter["launchHarness"]>[1]> = {}) =>
adapter.launchHarness(binding, { name: "seat", freshInPlace: true, ...opts });
describe("fresh in-place readiness scope and fallback", () => {
it.each(["adopted", "ordinary-fresh", "resume", "fork"])("preserves %s readiness without reading the current frame", async mode => {
const f = fixture();
if (mode === "ordinary-fresh") await f.adapter.launchHarness(binding, { name: "seat" });
if (mode === "resume") await launch(f.adapter, { resumeToken: token });
if (mode === "fork") await launch(f.adapter, { forkSource: { kind: "native_id", value: token } });
expect(await f.adapter.checkReady(binding)).toEqual({ ready: true });
expect(f.capturePaneScreen).not.toHaveBeenCalled();
});
it.each(["id", "nodeId", "tmuxSession", "tmuxPane", "launchGeneration"] as const)("does not reuse an intent for a different %s", async field => {
const f = fixture();
await launch(f.adapter);
const changed = { ...binding, [field]: field === "launchGeneration" ? 8 : "changed" };
expect(await f.adapter.checkReady(changed)).toEqual({ ready: true });
expect(f.capturePaneScreen).not.toHaveBeenCalled();
});
it.each([null, "", " "])("retains old admission when the current frame is unavailable (%s)", async screen => {
const f = fixture();
f.capturePaneScreen.mockResolvedValue(screen);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toEqual({ ready: true });
expect(f.capturePaneScreen).toHaveBeenCalledWith("%9");
});
it("retains old admission on a failed screen observation", async () => {
const f = fixture();
f.capturePaneScreen.mockRejectedValue(new Error("capture unavailable"));
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toEqual({ ready: true });
});
it.each([
["Accessing workspace:\nYes, I trust this folder", "trust_gate"],
["new MCP servers found in .mcp.json\nSelect any you wish to enable\nEnter to confirm", "mcp_gate"],
[CLAUDE_BYPASS_CONSENT, "bypass_consent_gate"],
["How would you like to resume?\n❯ Resume from summary\n Resume full session as-is", "claude_resume_selection_prompt"],
["● Not logged in · Please run /login\n❯\u00a0", "login_required"],
["No conversation found", "no_conversation_found"],
])("does not replace a visible gate with the history fallback: %s", async (screen, code) => {
const f = fixture();
f.capturePaneScreen.mockResolvedValue(screen);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toMatchObject({ ready: false, code });
});
it("does not promote a historically blocked result from a ready-looking current frame", async () => {
const f = fixture();
f.capturePaneContent.mockResolvedValue("Accessing workspace:\nYes, I trust this folder");
f.capturePaneScreen.mockResolvedValue(history);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toMatchObject({ ready: false, code: "trust_gate" });
expect(f.capturePaneScreen).not.toHaveBeenCalled();
});
it.each(["claude", "claude --unrecognized-option opaque --session-id apparent-token"])("keeps unknown identity unknown: %s", async command => {
for (const screen of [null, history,
"Restored conversation\n─────────\n❯ \n─────────\n ⏵⏵ auto mode on (shift+tab to cycle)",
"Restored conversation\n─────────\n❯ \n─────────\n ⏵⏵ accept edits on (shift+tab to cycle)"]) {
const f = fixture();
f.getPaneCommand.mockResolvedValue("sh");
f.capturePaneScreen.mockResolvedValue(screen);
const startedAt = "Mon Oct 19 21:30:00 2026";
f.listProcesses.mockReturnValue([
{ pid: 100, ppid: 1, pgid: 100, tpgid: 101, executableName: "bash", command: "-bash", startedAt },
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, executableName: "claude", command, startedAt },
]);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding), JSON.stringify({ command, screen })).toEqual({ ready: true });
}
});
it.each(["missing-pane-pid", "missing-pane-row", "background-claude"])("does not claim absence from incomplete pane evidence: %s", async mode => {
const f = fixture();
f.getPaneCommand.mockResolvedValue("sh");
f.capturePaneScreen.mockResolvedValue("❯ ");
const startedAt = "Mon Oct 19 21:30:00 2026";
f.listProcesses.mockReturnValue(mode === "missing-pane-row" ? [] : [
{ pid: 100, ppid: 1, pgid: 100, tpgid: 100, executableName: "bash", command: "-bash", startedAt },
...(mode === "background-claude" ? [
{ pid: 101, ppid: 100, pgid: 101, tpgid: 100, executableName: "claude", command: "claude", startedAt },
] : []),
]);
if (mode === "missing-pane-pid") f.getPanePid.mockResolvedValue(null);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toEqual({ ready: true });
});
it("does not use unavailable-screen fallback for a positively different launch identity", async () => {
const f = fixture();
f.capturePaneScreen.mockResolvedValue(null);
const startedAt = "Mon Oct 19 21:30:00 2026";
f.listProcesses.mockImplementation(() => [
{ pid: 100, ppid: 1, pgid: 100, tpgid: 101, executableName: "bash", command: "-bash", startedAt },
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, executableName: "claude", command: "claude --session-id different-token", startedAt },
]);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toMatchObject({ ready: false, code: "claude_launch_identity_conflict" });
});
it("keeps a positively different runtime blocked despite unavailable screen evidence", async () => {
const f = fixture();
f.capturePaneScreen.mockResolvedValue(null);
const startedAt = "Mon Oct 19 21:30:00 2026";
f.listProcesses.mockReturnValue([
{ pid: 100, ppid: 1, pgid: 100, tpgid: 101, executableName: "bash", command: "-bash", startedAt },
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, executableName: "codex", command: "codex resume other-token", startedAt },
]);
await launch(f.adapter);
expect(await f.adapter.checkReady(binding)).toMatchObject({ ready: false, code: "claude_launch_identity_conflict" });
});
it("does not carry a ready observation across a replacement launch", async () => {
const f = fixture();
await launch(f.adapter);
f.capturePaneScreen.mockImplementation(async () => {
await f.adapter.launchHarness(binding, { name: "replacement" });
return history;
});
expect(await f.adapter.checkReady(binding)).toMatchObject({ ready: false, code: "awaiting_runtime" });
});
});
@@ -0,0 +1,282 @@
// Reuses the released handover fixture. Process startup and pane bytes are modeled; no native Claude.
import { NativePermissionStore } from "../src/domain/native-permission-store.js";
import { ClaudeCodeAdapter } from "../src/adapters/claude-code-adapter.js";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const observations: unknown[] = [];
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from "vitest";
import type Database from "better-sqlite3";
import { createFullTestDb } from "./helpers/test-app.js";
import { RigRepository } from "../src/domain/rig-repository.js";
import { SessionRegistry } from "../src/domain/session-registry.js";
import { DiscoveryRepository } from "../src/domain/discovery-repository.js";
import { EventBus } from "../src/domain/event-bus.js";
import { SeatHandoverService } from "../src/domain/seat-handover-service.js";
import { TmuxAdapter } from "../src/adapters/tmux.js";
import type { RuntimeAdapter } from "../src/domain/runtime-adapter.js";
import { observeCodexSandbox } from "../src/domain/permission-drift.js";
import { type OccupantInvalidator } from "../src/domain/occupant-invalidator.js";
describe("Claude fresh in-place handover readiness", () => {
let db: Database.Database;
let rigRepo: RigRepository;
let sessionRegistry: SessionRegistry;
let discoveryRepo: DiscoveryRepository;
let eventBus: EventBus;
let hasSession: ReturnType<typeof vi.fn>;
let createSession: ReturnType<typeof vi.fn>;
let listPanes: ReturnType<typeof vi.fn>;
let killSession: ReturnType<typeof vi.fn>;
let respawnPane: ReturnType<typeof vi.fn>;
let setRemainOnExit: ReturnType<typeof vi.fn>;
let signalPaneProcess: ReturnType<typeof vi.fn>;
let isPaneDead: ReturnType<typeof vi.fn>;
let sendText: ReturnType<typeof vi.fn>;
let sendKeys: ReturnType<typeof vi.fn>;
let capturePaneScreen: ReturnType<typeof vi.fn>;
let launchHarness: ReturnType<typeof vi.fn>;
let checkReady: ReturnType<typeof vi.fn>;
let readSidecar: ReturnType<typeof vi.fn>;
let captureCodexThreadId: ReturnType<typeof vi.fn>;
let claudeProcessStartedAt: ((sessionName: string) => Promise<string | null>) | undefined;
let invalidateRetiringOccupant: ReturnType<typeof vi.fn>;
let declareOccupantSwap: ReturnType<typeof vi.fn>;
let resolvePredecessorRecap: ReturnType<typeof vi.fn>;
let getDefaultShell: ReturnType<typeof vi.fn>;
let getPaneCommand: ReturnType<typeof vi.fn>;
let service: SeatHandoverService;
beforeEach(() => {
db = createFullTestDb();
rigRepo = new RigRepository(db);
sessionRegistry = new SessionRegistry(db);
discoveryRepo = new DiscoveryRepository(db);
eventBus = new EventBus(db);
hasSession = vi.fn(async () => true);
createSession = vi.fn(async () => ({ ok: true }));
listPanes = vi.fn(async () => [{ id: "%9", index: 0, cwd: "/project", width: 80, height: 24, active: true }]);
killSession = vi.fn(async () => ({ ok: true }));
respawnPane = vi.fn(async () => ({ ok: true }));
setRemainOnExit = vi.fn(async () => ({ ok: true }));
signalPaneProcess = vi.fn(async () => ({ ok: true }));
isPaneDead = vi.fn(async () => true); // cutover: retiree exits gracefully on SIGTERM
sendText = vi.fn(async () => ({ ok: true }));
sendKeys = vi.fn(async () => ({ ok: true }));
capturePaneScreen = vi.fn(async () => "predecessor screen tail");
// B1 — a fresh successor is launched into a live agent (launchHarness +
// readiness) with a scraped resume token (B2 launched-mode).
launchHarness = vi.fn(async () => ({
ok: true,
resumeToken: "codex-launch-tok",
resumeType: "codex_id",
appliedLaunch: observeCodexSandbox(" -s workspace-write"),
}));
checkReady = vi.fn(async () => ({ ready: true }));
// B2 — discovered-mode derive-helper deps (Codex thread-id capturer by default).
readSidecar = vi.fn(() => ({ ok: true, data: { session_id: "claude-sid-123" } }));
captureCodexThreadId = vi.fn(async () => "codex-discovered-tok");
claudeProcessStartedAt = undefined;
invalidateRetiringOccupant = vi.fn();
declareOccupantSwap = vi.fn();
resolvePredecessorRecap = vi.fn(() => ({ unavailableReason: "test default: no record" }));
// KI-14: healthy default — the respawned pane comes up as a blank shell.
getDefaultShell = vi.fn(async () => "/bin/zsh");
getPaneCommand = vi.fn(async () => "zsh");
service = newService();
});
afterEach(() => {
db.close();
});
function tmux(): TmuxAdapter {
return { hasSession, createSession, listPanes, killSession, respawnPane, setRemainOnExit, signalPaneProcess, isPaneDead, sendText, sendKeys, capturePaneScreen, getDefaultShell, getPaneCommand } as unknown as TmuxAdapter;
}
function codexAdapter(): RuntimeAdapter {
return { runtime: "codex", launchHarness, checkReady } as unknown as RuntimeAdapter;
}
function newService(adapter: TmuxAdapter = tmux(), occupantInvalidator: OccupantInvalidator = { invalidateRetiringOccupant }): SeatHandoverService {
return new SeatHandoverService({
db,
rigRepo,
sessionRegistry,
discoveryRepo,
eventBus,
tmuxAdapter: adapter,
now: () => new Date("2026-04-24T18:30:00.000Z"),
newSuccessorId: () => "01SUCCID0",
runtimeAdapters: { codex: codexAdapter() },
contextUsageStore: { readSidecar } as never,
resumeTokenCapturer: { captureCodexThreadId } as never,
...(claudeProcessStartedAt ? { claudeProcessStartedAt } : {}),
occupantInvalidator,
activityOracle: { declareOccupantSwap },
predecessorRecapResolver: resolvePredecessorRecap as never,
readinessTimeoutMs: 50,
sleep: async () => {},
});
}
function seedSeat(opts?: { rigName?: string; runtime?: string; withSession?: boolean; model?: string; codexConfigProfile?: string; effort?: string }) {
const rig = rigRepo.createRig(opts?.rigName ?? "seat-rig");
const node = rigRepo.addNode(rig.id, "dev.impl", { runtime: opts?.runtime ?? "codex", cwd: "/project", model: opts?.model, codexConfigProfile: opts?.codexConfigProfile, effort: opts?.effort });
let sessionId: string | null = null;
if (opts?.withSession !== false) {
const session = sessionRegistry.registerSession(node.id, `dev-impl@${rig.name}`);
sessionRegistry.updateStatus(session.id, "running");
sessionRegistry.updateStartupStatus(session.id, "ready", "2026-04-20T12:00:00Z");
sessionRegistry.updateBinding(node.id, { tmuxSession: `dev-impl@${rig.name}`, tmuxPane: "%0" });
sessionId = session.id;
}
return { rig, node, sessionId };
}
it.each(["classic-predecessor-history", "screen-without-predecessor-history", "new-runtime-already-ready", "stale-wrapper-screen-unavailable", "working-wrapper-screen-unavailable", "working-headerless-auto-proof", "working-headerless-auto-unavailable", "working-headerless-accept-edits-unavailable", "working-headerless-bypass-unavailable",
...["shell", "script"].flatMap(process => ["prompt", "command"].flatMap(frame =>
["readable", "unreadable"].map(listing => `classic-chevron-${process}-${frame}-${listing}`))),
])("1544 %s", async mode => {
const { node } = seedSeat({ runtime: "claude-code" });
let elapsed = 0, requested = false, packetPasted = false;
const chevron = mode.startsWith("classic-chevron-");
const script = chevron && mode.includes("-script-");
const unreadable = chevron && mode.endsWith("-unreadable");
const shellPrompt = chevron ? mode.includes("-command-") ? "❯ /bin/sh fixture-launch.txt" : "❯ " : "operator@host$ ";
const headerless = mode.startsWith("working-headerless-");
const auto = mode.startsWith("working-headerless-auto");
const bypass = mode === "working-headerless-bypass-unavailable";
if (auto || bypass) new NativePermissionStore(db).write(node.id, { runtime: "claude-code", mode: auto ? "auto" : "bypassPermissions" }, "fixture", "headerless qualification");
const nativeAt = mode === "new-runtime-already-ready" || mode === "working-wrapper-screen-unavailable" || headerless ? 0 : 500;
const unavailable = mode.endsWith("screen-unavailable");
const identicalFrame = "Claude Code v2.1.303\n❯ \n ⏵⏵ accept edits on (shift+tab to cycle)";
const autoFrame = "Restored conversation\n─────────\n❯ \n─────────\n ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents";
const headerlessFrame = auto ? autoFrame : autoFrame.replace("auto mode on", bypass ? "bypass permissions on" : "accept edits on");
const oldFrame = "Claude Code v2.1.303\n❯ predecessor finished turn\n ⏵⏵ accept edits on (shift+tab to cycle)";
const freshFrame = "Claude Code v2.1.303\n❯ \n ⏵⏵ accept edits on (shift+tab to cycle)";
const trace: unknown[] = [];
const pane = () => headerless ? identicalFrame + "\n" + headerlessFrame : unavailable ? identicalFrame : requested && elapsed >= nativeAt ? freshFrame
: mode === "classic-predecessor-history" || chevron ? oldFrame + "\n" + shellPrompt : shellPrompt;
getPaneCommand.mockImplementation(async () => (unavailable || headerless) && requested ? "sh" : requested && elapsed >= nativeAt ? "claude" : requested && script ? "sh" : "zsh");
capturePaneScreen.mockImplementation(async (target: string) => {
const returnedPane = unavailable ? null : headerless ? headerlessFrame : requested && elapsed < nativeAt ? shellPrompt : pane();
trace.push({ kind: "current-frame", target, elapsed, returnedPane });
return returnedPane;
});
const capturePaneContent = vi.fn(async (_session: string, lines: number) => {
trace.push({ kind: "history", elapsed, lines, returnedPane: pane() });
return pane();
});
const target = { ...tmux(), capturePaneContent, getPanePid: vi.fn(async () => 100),
sendShellCommand: async (session: string, text: string) => sendText(session, text) } as unknown as TmuxAdapter;
const fsError = () => { throw new Error("unexpected filesystem access"); };
const listProcesses = vi.fn(() => {
if (unavailable || unreadable || mode.endsWith("-unavailable")) {
trace.push({ kind: "process-read", elapsed, available: false });
throw new Error("fixture ps unavailable");
}
const startedAt = "Mon Oct 19 21:30:00 2026";
if (chevron) {
const running = requested && elapsed >= nativeAt;
const foreground = script ? 101 : running ? 102 : 100;
const rows = [{ pid: 100, ppid: 1, pgid: 100, tpgid: foreground,
command: "-zsh", executableName: "zsh", startedAt },
...(script ? [{ pid: 101, ppid: 100, pgid: 101, tpgid: 101,
command: "/bin/sh fixture-launch.txt", executableName: "sh", startedAt }] : []),
...(running ? [{ pid: 102, ppid: script ? 101 : 100, pgid: foreground, tpgid: foreground,
command: `claude --session-id ${successorToken}`, executableName: "claude", startedAt }] : [])];
trace.push({ kind: "process-read", elapsed, available: true, rows });
return rows;
}
if (mode !== "working-headerless-auto-proof") return [];
return [
{ pid: 100, ppid: 1, pgid: 100, tpgid: 101, command: "-bash", executableName: "bash", startedAt },
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, command: "/bin/sh fixture-launch.txt", executableName: "sh", startedAt },
{ pid: 102, ppid: 101, pgid: 101, tpgid: 101, command: `/opt/claude.exe --permission-mode auto --session-id ${successorToken} --name dev-impl@seat-rig`, executableName: "claude", startedAt },
];
});
const claude = new ClaudeCodeAdapter({ tmux: target, fsOps: { exists: () => false, readFile: fsError,
writeFile: fsError, mkdirp: fsError, copyFile: fsError }, listProcesses, sleep: async () => {}, sessionIdFactory: () => successorToken,
...(headerless ? { claudeManagedLaunch: { prepare: async () => ({ command: (args: readonly string[]) => `claude ${args.join(" ")}`,
assertCurrent: () => {}, configDir: "/fixture" }) } as never } : {}) });
const reads: unknown[] = [];
const readiness = vi.fn(async (binding) => {
const result = await claude.checkReady(binding);
reads.push({ elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, result });
return result;
});
const successorToken = "22222222-2222-4222-8222-222222222222";
sendText.mockImplementation(async (session: string, text: string) => {
if (text.includes("claude ") && text.includes("--session-id")) {
requested = true; trace.push({ kind: "launch-text", elapsed, session, text, nativeAt });
return { ok: true };
}
packetPasted = true;
trace.push({ kind: "packet-paste", session, elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, text });
return { ok: true };
});
sendKeys.mockImplementation(async (session: string, keys: string[]) => {
trace.push({ kind: packetPasted ? "input" : "launch-input", session, elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, keys });
return { ok: true };
});
const handover = new SeatHandoverService({ db, rigRepo, sessionRegistry, discoveryRepo, eventBus,
tmuxAdapter: target, now: () => new Date("2026-10-19T21:30:00Z"), newSuccessorId: () => "01SUCCID0",
runtimeAdapters: { "claude-code": { runtime: "claude-code", launchHarness: (binding, opts) => { trace.push({ kind: "launch-intent", opts }); return claude.launchHarness(binding, opts); }, checkReady: readiness } as unknown as RuntimeAdapter },
contextUsageStore: { readSidecar } as never, resumeTokenCapturer: { captureCodexThreadId } as never,
occupantInvalidator: { invalidateRetiringOccupant }, activityOracle: { declareOccupantSwap },
predecessorRecapResolver: resolvePredecessorRecap as never, readinessTimeoutMs: 5000,
sleep: async ms => { elapsed += ms; trace.push({ kind: "sleep", ms, elapsed }); },
});
const result = await handover.handover({ seatRef: "dev-impl@seat-rig", source: "fresh", reason: "bounded scrollback reproduction" });
const sessions = db.prepare("SELECT session_name,status,resume_token,startup_status FROM sessions ORDER BY id").all();
observations.push({ mode, result, trace, reads, listProcessesCalls: listProcesses.mock.calls.length,
sessions, respawn: respawnPane.mock.calls });
expect(result.ok).toBe(true);
expect(reads.length).toBeGreaterThan(0);
const first = reads[0] as any;
if (chevron) {
expect(first.result.ready).toBe(unreadable);
expect(first.successorRuntimeStarted).toBe(false);
const packet = trace.find((x: any) => x.kind === "packet-paste") as any;
expect(packet.elapsed).toBe(unreadable ? 0 : 1000);
expect(packet.successorRuntimeStarted).toBe(!unreadable);
expect(trace).toContainEqual({ kind: "current-frame", target: "%9", elapsed: 0, returnedPane: shellPrompt });
expect(trace.some((x: any) => x.kind === "process-read" && x.available === !unreadable)).toBe(true);
} else if (mode === "classic-predecessor-history") {
expect(first.result.ready).toBe(false);
expect(first.successorRuntimeStarted).toBe(false);
expect((trace.find((x: any) => x.kind === "packet-paste") as any).elapsed).toBeGreaterThanOrEqual(nativeAt);
expect((reads.at(-1) as any).result.ready).toBe(true);
expect(trace).toContainEqual({ kind: "current-frame", target: "%9", elapsed: 0, returnedPane: "operator@host$ " });
} else if (headerless) {
const launched = trace.find((x: any) => x.kind === "launch-text") as any;
if (auto) expect(launched.text).toMatch(/--permission-mode auto(?: |$)/);
if (bypass) expect(launched.text).toMatch(/--permission-mode bypassPermissions(?: |$)/);
if (!auto && !bypass) expect(launched.text).toMatch(/--permission-mode acceptEdits(?: |$)/);
expect(first.result.ready).toBe(true);
expect(first.successorRuntimeStarted).toBe(true);
expect(capturePaneScreen).toHaveBeenCalledWith("%9");
} else if (unavailable) {
// O1 deliberately preserves admission for this indistinguishable stale/working pair.
expect(first.result.ready).toBe(true);
expect(first.successorRuntimeStarted).toBe(mode === "working-wrapper-screen-unavailable");
expect(capturePaneScreen).toHaveBeenCalledWith("%9");
} else if (mode === "new-runtime-already-ready") expect(first.result.ready).toBe(true);
else {
expect(first.result.ready).toBe(false);
expect((reads.at(-1) as any).result.ready).toBe(true);
}
expect(trace.find((x: any) => x.kind === "launch-intent")).toMatchObject({ opts: { freshInPlace: true } });
expect(respawnPane).toHaveBeenCalled();
expect(killSession).not.toHaveBeenCalled();
});
});
afterAll(() => {
const dir = process.env.CHECKPOINT_OUTPUT;
if (!dir) return;
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "observations.json"), JSON.stringify(observations, null, 2) + "\n");
});
@@ -0,0 +1,120 @@
// Diagnosis only: unchanged production source, modeled captures and input effects.
import { afterAll, describe, expect, it, vi } from "vitest";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { RigRepository } from "../src/domain/rig-repository.js";
import { SessionRegistry } from "../src/domain/session-registry.js";
import { AgentActivityStore } from "../src/domain/agent-activity-store.js";
import { EventBus } from "../src/domain/event-bus.js";
import { SessionTransport, classifyPaneActivity, probeSessionActivity, readInputBox, readSeatBeforePaste } from "../src/domain/session-transport.js";
import { findComposerInputLineIndex } from "../src/domain/composer-prompts.js";
import type { TmuxAdapter } from "../src/adapters/tmux.js";
import { createFullTestDb } from "./helpers/test-app.js";
const quoted = "• Ran rig capture peer\n › 1. Yes, proceed (y)\n 2. No (esc)\n Press enter to confirm or esc to cancel";
const working = "• Working (23s • esc to interrupt)";
const reported = `${quoted}\n${working}\n› Ask Codex to do anything\nGPT-6-Astra default\n? for shortcuts`;
const supportedBox = "› Ask Codex to do anything\n\n GPT-6-Astra default · ~/code\n ? for shortcuts";
const supported = `${quoted}\n${working}\n\n${supportedBox}`;
const menu = "Would you like to run the following command?\n› 1. Yes, proceed (y)\n 2. No (esc)\nPress enter to confirm or esc to cancel";
const rule = "─".repeat(60);
const claudeBar = " ⏵⏵ accept edits on (shift+tab to cycle)";
const oldQuote = "● Earlier report: Do you want to proceed?";
const claudeFrame = (head: string, draft = "") => `${oldQuote}\n\n${head}\n\n${rule}\n❯ ${draft}\n${rule}\n${claudeBar}`;
type Case = { id: string; pane: string | null; runtime?: "codex" | "claude-code"; current?: boolean; fault?: boolean };
const cases: Case[] = [
{ id: "reported-synthetic", pane: reported },
{ id: "supported-bottom-frame", pane: supported },
{ id: "supported-idle-bottom-frame", pane: `${quoted}\n\n${supportedBox}` },
{ id: "reported-without-quoted-choice", pane: `${working}\n› Ask Codex to do anything\nGPT-6-Astra default\n? for shortcuts` },
{ id: "current-codex-dialog", pane: menu, current: true },
{ id: "working-prose-before-dialog", pane: `${working}\n${menu}`, current: true },
{ id: "working-prose-after-dialog", pane: `${menu}\n${working}`, current: true },
{ id: "current-dialog-under-status", pane: `${working}\n${menu}\n\n${supportedBox}`, current: true },
// Equal bytes, alternate underlying meaning: no classifier can distinguish intent labels.
{ id: "supported-equal-render-current-dialog", pane: supported, current: true },
{ id: "clipped-codex-permission", pane: "Would you like to run the following command?\n GPT-6-Astra default", current: true },
{ id: "tall-claude-picker", runtime: "claude-code", pane: ["Choose", "❯ 1. Yes", ...Array.from({ length: 18 }, () => " option description"), " 2. No", "Enter to select · ↑/↓ to navigate · Esc to cancel"].join("\n"), current: true },
{ id: "clipped-claude-permission", runtime: "claude-code", pane: `Do you want to proceed?\n${rule}\n❯ \n${rule}\n${claudeBar}`, current: true },
{ id: "codex-draft", pane: supportedBox.replace("Ask Codex to do anything", "human draft") },
{ id: "quoted-choice-in-draft", pane: supportedBox.replace("Ask Codex to do anything", "human draft\n › 1. Yes, proceed (y)") },
{ id: "bare-choice-history-unknown-frame", pane: `${quoted}\nunknown footer` },
{ id: "bare-working-unknown-frame", pane: `${working}\nunknown footer` },
{ id: "unknown-frame", pane: "Unrecognized terminal content" },
{ id: "null-capture", pane: null },
{ id: "failed-capture", pane: null, fault: true },
// Useful #1323 boundary controls, recreated without its unmerged production patch.
{ id: "1323-completed-claude-history", runtime: "claude-code", pane: claudeFrame("✻ Crunched for 2s") },
{ id: "1323-current-question", runtime: "claude-code", pane: claudeFrame("Do you want to proceed?"), current: true },
{ id: "1323-draft", runtime: "claude-code", pane: claudeFrame("✻ Crunched for 2s", "typed draft") },
{ id: "1323-unknown-footer", runtime: "claude-code", pane: claudeFrame("✻ Crunched for 2s").replace(claudeBar, "unknown footer") },
];
for (const name of ["after-enter-0.4s.J.txt", "after-enter-15s.J.txt"]) {
const pane = readFileSync(new URL(`./fixtures/input-box/codex-0.160.0/${name}`, import.meta.url), "utf8");
const lines = pane.split("\n");
const at = findComposerInputLineIndex(lines, "›»");
if (at < 0) throw new Error("retained fixture lacks composer");
lines.splice(at, 0, quoted, working, "");
cases.push({ id: `shipped-frame-plus-synthetic-quote:${name}`, pane: lines.join("\n") });
}
const rows: unknown[] = [];
afterAll(() => {
const dir = process.env.CHECKPOINT_OUTPUT;
if (!dir) throw new Error("private evidence output required");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "observations.json"), JSON.stringify(rows, null, 2) + "\n");
});
describe("1533 unchanged classifier and guarded-send diagnosis", () => {
for (const hook of ["none", "running", "permission"] as const) {
it.each(cases)(`${hook}: $id`, async fixture => {
const runtime = fixture.runtime ?? "codex";
const pane = fixture.pane;
const db = createFullTestDb();
try {
const now = new Date("2026-10-19T15:30:00Z");
const rigRepo = new RigRepository(db), sessionRegistry = new SessionRegistry(db), eventBus = new EventBus(db);
const store = new AgentActivityStore({ db, eventBus });
const rig = rigRepo.createRig("diag1533");
const node = rigRepo.addNode(rig.id, "dev.test", { role: "worker", runtime });
const session = sessionRegistry.registerSession(node.id, "dev-test@diag1533");
sessionRegistry.updateStatus(session.id, "running");
sessionRegistry.updateBinding(node.id, { tmuxSession: "dev-test@diag1533" });
if (hook !== "none") store.recordHookEvent({ runtime, sessionName: "dev-test@diag1533",
hookEvent: hook === "running" ? "UserPromptSubmit" : runtime === "codex" ? "PermissionRequest" : "Notification",
...(hook === "permission" && runtime === "claude-code" ? { subtype: "permission_prompt" } : {}), occurredAt: now.toISOString() });
const effects: unknown[] = [];
const sendText = vi.fn(async (_target: string, value: string) => { effects.push({ paste: value }); return { ok: true as const }; });
const sendKeys = vi.fn(async (_target: string, value: string[]) => { effects.push({ keys: value }); return { ok: true as const }; });
const tmux = { hasSession: async () => true, probeSession: async () => ({ state: "present" as const }),
capturePaneContent: async () => { if (fixture.fault) throw new Error("controlled failed capture"); return pane; },
sendText, sendKeys, getPaneCommand: async () => null, getPanePid: async () => null,
listSessions: async () => [], listWindows: async () => [], listPanes: async () => [],
} as unknown as TmuxAdapter;
const classifier = classifyPaneActivity(pane ?? "", { runtime });
const probe = await probeSessionActivity({ sessionName: "dev-test@diag1533", runtime, attachmentType: "tmux", tmuxAdapter: tmux, now });
const transport = new SessionTransport({ db, rigRepo, sessionRegistry, eventBus, agentActivityStore: store,
tmuxAdapter: tmux, now: () => now, sleep: async () => {} });
const result = await transport.send("dev-test@diag1533", "ordinary status update");
// Counterfactual scope experiment only, NOT a production correction. A supported box and
// status locate a suffix; the equal-render control exposes what that DOES NOT prove.
const inputBox = readInputBox(pane, runtime, "ordinary status update");
const lines = (pane ?? "").split("\n");
const inputAt = findComposerInputLineIndex(lines, runtime === "codex" ? "›»" : "❯");
const statusAt = lines.findLastIndex((line, i) => i < inputAt && /^• Working \([^\n]*esc to interrupt\)/.test(line.trim()));
const proposedBoundary = runtime === "codex" && inputBox === "empty" && statusAt >= 0 ? statusAt : null;
const suffixVerdict = proposedBoundary === null ? null : classifyPaneActivity(lines.slice(proposedBoundary).join("\n"), { runtime });
rows.push({ id: fixture.id, hook, runtime, pane, currentDialogIntent: fixture.current ?? false,
classifier, probe, result, effects, pasteCount: sendText.mock.calls.length, keyCount: sendKeys.mock.calls.length,
inputBox, seatBeforePaste: readSeatBeforePaste(pane, runtime), inputAt, statusAt, proposedBoundary, suffixVerdict });
if (fixture.id === "reported-synthetic") expect(classifier).toMatchObject({ state: "attention", reason: "selection_prompt" });
if (hook === "permission" || (fixture.current && hook === "none")) {
expect(result).toMatchObject({ ok: false, reason: "target_needs_input" });
expect(sendText).not.toHaveBeenCalled(); expect(sendKeys).not.toHaveBeenCalled();
}
if (fixture.id === "reported-synthetic" && hook === "none") expect(result).toMatchObject({ ok: false, reason: "target_needs_input" });
} finally { db.close(); }
});
}
});
@@ -0,0 +1,123 @@
// Bounded diagnosis only: reported menu text, modeled pane/process reads, no native Claude or approval input.
import Database from "better-sqlite3";
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { TmuxAdapter } from "../src/adapters/tmux.js";
import { ClaudeCodeAdapter } from "../src/adapters/claude-code-adapter.js";
import { ALL_MIGRATIONS } from "../src/db/all-migrations.js";
import { migrate } from "../src/db/migrate.js";
import { AgentActivityStore } from "../src/domain/agent-activity-store.js";
import { EventBus } from "../src/domain/event-bus.js";
import { assessNativeResumeProbe } from "../src/domain/native-resume-probe.js";
import { RigRepository } from "../src/domain/rig-repository.js";
import { SeatDeliveryGuard, resolveGuardTarget } from "../src/domain/seat-delivery-guard.js";
import { SessionRegistry } from "../src/domain/session-registry.js";
import { classifyPaneActivity, SessionTransport } from "../src/domain/session-transport.js";
import { transportRoutes } from "../src/routes/transport.js";
const oldMenu = ` 2 new MCP servers found in .mcp.json
Select any you wish to enable.
MCP servers may execute code or access system resources. All tool calls
require approval. Learn more in the MCP documentation.
❯ [✔] dummy1
[✔] dummy2
Space to select · Enter to confirm · Esc to reject all`;
const newMenu = oldMenu.replace("in .mcp.json", "in this project")
.replace(" Space to select · Enter to confirm · Esc to reject all", " Enable selected\n Space to select · Esc to reject all");
const rule = "─".repeat(60);
const composer = `Claude Code v2.1.303\n${rule}\n❯ \n${rule}\n ⏵⏵ accept edits on (shift+tab to cycle)`;
const frames = [
{ id: "old-current-MCP", pane: oldMenu },
{ id: "2.1.303-current-MCP", pane: newMenu },
{ id: "genuine-composer", pane: composer },
{ id: "old-MCP-history-before-composer", pane: `${oldMenu}\n\n${composer}` },
{ id: "new-MCP-history-before-composer", pane: `${newMenu}\n\n${composer}` },
{ id: "ordinary-current-permission", pane: "Do you want to proceed?\n❯ 1. Yes\n 2. No\nEnter to select · ↑/↓ to navigate · Esc to cancel" },
{ id: "MCP-prose-before-composer", pane: `The project has new MCP servers. Select any you wish to enable.\n\n${composer}` },
{ id: "clipped-new-MCP", pane: newMenu.split("\n").slice(0, 3).join("\n") },
{ id: "current-checkbox-cursor-without-normal-composer", pane: "Choose\n❯ [✔] option\nSpace to select · Esc to reject all" },
];
const cleanup: Array<() => void | Promise<void>> = [];
const records: unknown[] = [];
afterEach(async () => { for (const close of cleanup.splice(0).reverse()) await close(); vi.restoreAllMocks(); });
afterAll(() => {
const dir = process.env.CHECKPOINT_OUTPUT;
if (!dir) throw new Error("diagnostic output required");
mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, "observations.json"), JSON.stringify(records, null, 2) + "\n");
});
describe("1541 actual assessment, checkReady and HTTP send observations", () => {
it.each(frames)("$id", async frame => {
const db = new Database(":memory:"); cleanup.push(() => db.close()); migrate(db, ALL_MIGRATIONS);
db.exec(`INSERT INTO rigs(id,name) VALUES ('rig','test');
INSERT INTO nodes(id,rig_id,logical_id,runtime) VALUES ('node','rig','worker','claude-code');
INSERT INTO bindings(id,node_id,tmux_session,tmux_pane) VALUES ('binding','node','worker@test','%1');
INSERT INTO sessions(id,node_id,session_name,status) VALUES ('session','node','worker@test','running');
INSERT INTO occupant_tenures(id,node_id,generation_ordinal,generation_uuid,kind) VALUES ('generation','node',1,'g1','fresh');`);
const guard = new SeatDeliveryGuard(db, name => resolveGuardTarget(db, name)); guard.recoverActivation();
let staged = "", command = "claude";
const effects: Array<{ kind: string; argv: string[]; text?: string }> = [];
const tmux = new TmuxAdapter(async () => "", {
writeFile: async (_path, text) => { staged = text; }, unlink: async () => {}, tmpName: () => "/fixture/input", bufferName: () => "input",
}, async argv => {
if (argv[1] === "paste-buffer") effects.push({ kind: "paste", argv: [...argv], text: staged });
if (argv[1] === "send-keys") effects.push({ kind: "keys", argv: [...argv] });
return "";
});
tmux.deliveryGuard = guard;
vi.spyOn(tmux, "probeSession").mockResolvedValue({ state: "present" });
vi.spyOn(tmux, "listPanes").mockResolvedValue([{ id: "%1" } as never]);
vi.spyOn(tmux, "getPaneCommand").mockImplementation(async () => command);
vi.spyOn(tmux, "getPanePid").mockResolvedValue(null);
vi.spyOn(tmux, "capturePaneContent").mockResolvedValue(frame.pane);
const fsError = () => { throw new Error("unexpected filesystem effect"); };
const adapter = new ClaudeCodeAdapter({ tmux, fsOps: {
exists: () => false, readFile: fsError, writeFile: fsError, mkdirp: fsError, copyFile: fsError,
}, listProcesses: () => [], sleep: async () => {} });
const binding = { id: "binding", nodeId: "node", tmuxSession: "worker@test", tmuxWindow: null, tmuxPane: "%1",
cmuxWorkspace: null, cmuxSurface: null, updatedAt: "", cwd: "/fixture" };
const assessments = [
{ paneCommand: "bash", verified: false }, { paneCommand: "bash", verified: true }, { paneCommand: "claude", verified: false },
].map(input => ({ ...input, result: assessNativeResumeProbe({ runtime: "claude-code", paneContent: frame.pane,
paneCommand: input.paneCommand, ...(input.verified ? { claudeResumeIdentityVerified: true } : {}) }) }));
const readiness: unknown[] = [];
for (const paneCommand of ["bash", "claude"]) { command = paneCommand; readiness.push({ paneCommand, result: await adapter.checkReady(binding) }); }
command = "claude";
const now = new Date("2026-10-19T19:30:00Z");
const eventBus = new EventBus(db), activity = new AgentActivityStore({ db, eventBus });
const transport = new SessionTransport({ db, rigRepo: new RigRepository(db), sessionRegistry: new SessionRegistry(db), tmuxAdapter: tmux,
eventBus, agentActivityStore: activity, now: () => now, sleep: async () => {} });
const app = new Hono();
app.use("*", async (c, next) => { c.set("sessionTransport" as never, transport); await next(); });
app.route("/api/transport", transportRoutes());
const server = serve({ fetch: app.fetch, port: 0, hostname: "127.0.0.1" }) as Server;
cleanup.push(() => new Promise<void>((resolve, reject) => { server.closeAllConnections(); server.close(error => error ? reject(error) : resolve()); }));
await new Promise<void>(resolve => server.once("listening", resolve));
const { port } = server.address() as AddressInfo;
const sends: unknown[] = [];
for (const hook of ["none", "running", "permission"] as const) {
if (hook !== "none") activity.recordHookEvent({ runtime: "claude-code", sessionName: "worker@test",
hookEvent: hook === "running" ? "UserPromptSubmit" : "Notification", ...(hook === "permission" ? { subtype: "permission_prompt" } : {}), occurredAt: now.toISOString() });
effects.length = 0;
const response = await fetch(`http://127.0.0.1:${port}/api/transport/send`, { method: "POST",
headers: { "content-type": "application/json", "x-openrig-session": "sender@test" },
body: JSON.stringify({ session: "worker@test", text: "ordinary status update" }) });
const body = await response.json();
sends.push({ hook, status: response.status, body, effects: structuredClone(effects) });
expect(response.status).toBe(body.ok ? 200 : 409);
expect(effects.map(x => x.kind)).toEqual(body.ok ? ["paste", "keys"] : []);
if (hook === "permission") expect(body).toMatchObject({ ok: false, reason: "target_needs_input" });
if (frame.id === "2.1.303-current-MCP" && hook === "none") expect(body.ok).toBe(true); // Reproduced defect, NOT desired safety behavior.
}
if (frame.id === "old-current-MCP") expect(assessments[0]!.result).toMatchObject({ status: "inconclusive", code: "mcp_gate" });
if (frame.id === "2.1.303-current-MCP") expect(assessments[1]!.result).toMatchObject({ status: "resumed", code: "verified_native_identity" });
records.push({ id: frame.id, pane: frame.pane, assessments, readiness, classifier: classifyPaneActivity(frame.pane, { runtime: "claude-code" }), sends });
});
});
@@ -0,0 +1,256 @@
// Reuses the released handover fixture. Process startup and pane bytes are modeled; no native Claude.
import { NativePermissionStore } from "../src/domain/native-permission-store.js";
import { ClaudeCodeAdapter } from "../src/adapters/claude-code-adapter.js";
import { assessNativeResumeProbe } from "../src/domain/native-resume-probe.js";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const observations: unknown[] = [];
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from "vitest";
import type Database from "better-sqlite3";
import { createFullTestDb, createTestApp } from "./helpers/test-app.js";
import { RigRepository } from "../src/domain/rig-repository.js";
import { SessionRegistry } from "../src/domain/session-registry.js";
import { DiscoveryRepository } from "../src/domain/discovery-repository.js";
import { EventBus } from "../src/domain/event-bus.js";
import { SeatHandoverService } from "../src/domain/seat-handover-service.js";
import { WatchdogAutoRegistration } from "../src/domain/watchdog-auto-registration.js";
import { WatchdogJobsRepository } from "../src/domain/watchdog-jobs-repository.js";
import { WatchdogHistoryLog } from "../src/domain/watchdog-history-log.js";
import { WatchdogPolicyEngine, type DeliveryFn } from "../src/domain/watchdog-policy-engine.js";
import { makeIdleGateQitemPolicy } from "../src/domain/policies/idle-gate-qitem.js";
import { AgentActivityStore } from "../src/domain/agent-activity-store.js";
import { watchdogHistorySchema } from "../src/db/migrations/032_watchdog_history.js";
import { TmuxAdapter } from "../src/adapters/tmux.js";
import type { RuntimeAdapter } from "../src/domain/runtime-adapter.js";
import { observeCodexSandbox } from "../src/domain/permission-drift.js";
import { AppliedLaunchObservationStore } from "../src/domain/applied-launch-observation-store.js";
import { QueueRepository } from "../src/domain/queue-repository.js";
import { DefaultOccupantInvalidator, type OccupantInvalidator } from "../src/domain/occupant-invalidator.js";
describe("SeatHandoverService", () => {
let db: Database.Database;
let rigRepo: RigRepository;
let sessionRegistry: SessionRegistry;
let discoveryRepo: DiscoveryRepository;
let eventBus: EventBus;
let hasSession: ReturnType<typeof vi.fn>;
let createSession: ReturnType<typeof vi.fn>;
let listPanes: ReturnType<typeof vi.fn>;
let killSession: ReturnType<typeof vi.fn>;
let respawnPane: ReturnType<typeof vi.fn>;
let setRemainOnExit: ReturnType<typeof vi.fn>;
let signalPaneProcess: ReturnType<typeof vi.fn>;
let isPaneDead: ReturnType<typeof vi.fn>;
let sendText: ReturnType<typeof vi.fn>;
let sendKeys: ReturnType<typeof vi.fn>;
let capturePaneScreen: ReturnType<typeof vi.fn>;
let launchHarness: ReturnType<typeof vi.fn>;
let checkReady: ReturnType<typeof vi.fn>;
let readSidecar: ReturnType<typeof vi.fn>;
let captureCodexThreadId: ReturnType<typeof vi.fn>;
let claudeProcessStartedAt: ((sessionName: string) => Promise<string | null>) | undefined;
let invalidateRetiringOccupant: ReturnType<typeof vi.fn>;
let declareOccupantSwap: ReturnType<typeof vi.fn>;
let resolvePredecessorRecap: ReturnType<typeof vi.fn>;
let getDefaultShell: ReturnType<typeof vi.fn>;
let getPaneCommand: ReturnType<typeof vi.fn>;
let service: SeatHandoverService;
beforeEach(() => {
db = createFullTestDb();
rigRepo = new RigRepository(db);
sessionRegistry = new SessionRegistry(db);
discoveryRepo = new DiscoveryRepository(db);
eventBus = new EventBus(db);
hasSession = vi.fn(async () => true);
createSession = vi.fn(async () => ({ ok: true }));
listPanes = vi.fn(async () => [{ id: "%9", index: 0, cwd: "/project", width: 80, height: 24, active: true }]);
killSession = vi.fn(async () => ({ ok: true }));
respawnPane = vi.fn(async () => ({ ok: true }));
setRemainOnExit = vi.fn(async () => ({ ok: true }));
signalPaneProcess = vi.fn(async () => ({ ok: true }));
isPaneDead = vi.fn(async () => true); // cutover: retiree exits gracefully on SIGTERM
sendText = vi.fn(async () => ({ ok: true }));
sendKeys = vi.fn(async () => ({ ok: true }));
capturePaneScreen = vi.fn(async () => "predecessor screen tail");
// B1 — a fresh successor is launched into a live agent (launchHarness +
// readiness) with a scraped resume token (B2 launched-mode).
launchHarness = vi.fn(async () => ({
ok: true,
resumeToken: "codex-launch-tok",
resumeType: "codex_id",
appliedLaunch: observeCodexSandbox(" -s workspace-write"),
}));
checkReady = vi.fn(async () => ({ ready: true }));
// B2 — discovered-mode derive-helper deps (Codex thread-id capturer by default).
readSidecar = vi.fn(() => ({ ok: true, data: { session_id: "claude-sid-123" } }));
captureCodexThreadId = vi.fn(async () => "codex-discovered-tok");
claudeProcessStartedAt = undefined;
invalidateRetiringOccupant = vi.fn();
declareOccupantSwap = vi.fn();
resolvePredecessorRecap = vi.fn(() => ({ unavailableReason: "test default: no record" }));
// KI-14: healthy default — the respawned pane comes up as a blank shell.
getDefaultShell = vi.fn(async () => "/bin/zsh");
getPaneCommand = vi.fn(async () => "zsh");
service = newService();
});
afterEach(() => {
db.close();
});
function tmux(): TmuxAdapter {
return { hasSession, createSession, listPanes, killSession, respawnPane, setRemainOnExit, signalPaneProcess, isPaneDead, sendText, sendKeys, capturePaneScreen, getDefaultShell, getPaneCommand } as unknown as TmuxAdapter;
}
function codexAdapter(): RuntimeAdapter {
return { runtime: "codex", launchHarness, checkReady } as unknown as RuntimeAdapter;
}
function newService(adapter: TmuxAdapter = tmux(), occupantInvalidator: OccupantInvalidator = { invalidateRetiringOccupant }): SeatHandoverService {
return new SeatHandoverService({
db,
rigRepo,
sessionRegistry,
discoveryRepo,
eventBus,
tmuxAdapter: adapter,
now: () => new Date("2026-04-24T18:30:00.000Z"),
newSuccessorId: () => "01SUCCID0",
runtimeAdapters: { codex: codexAdapter() },
contextUsageStore: { readSidecar } as never,
resumeTokenCapturer: { captureCodexThreadId } as never,
...(claudeProcessStartedAt ? { claudeProcessStartedAt } : {}),
occupantInvalidator,
activityOracle: { declareOccupantSwap },
predecessorRecapResolver: resolvePredecessorRecap as never,
readinessTimeoutMs: 50,
sleep: async () => {},
});
}
function seedSeat(opts?: { rigName?: string; runtime?: string; withSession?: boolean; model?: string; codexConfigProfile?: string; effort?: string }) {
const rig = rigRepo.createRig(opts?.rigName ?? "seat-rig");
const node = rigRepo.addNode(rig.id, "dev.impl", { runtime: opts?.runtime ?? "codex", cwd: "/project", model: opts?.model, codexConfigProfile: opts?.codexConfigProfile, effort: opts?.effort });
let sessionId: string | null = null;
if (opts?.withSession !== false) {
const session = sessionRegistry.registerSession(node.id, `dev-impl@${rig.name}`);
sessionRegistry.updateStatus(session.id, "running");
sessionRegistry.updateStartupStatus(session.id, "ready", "2026-04-20T12:00:00Z");
sessionRegistry.updateBinding(node.id, { tmuxSession: `dev-impl@${rig.name}`, tmuxPane: "%0" });
sessionId = session.id;
}
return { rig, node, sessionId };
}
it.each(["classic-predecessor-history", "screen-without-predecessor-history", "new-runtime-already-ready", "stale-wrapper-screen-unavailable", "working-wrapper-screen-unavailable", "working-headerless-auto-proof", "working-headerless-auto-unavailable"])("1544 %s", async mode => {
const { node } = seedSeat({ runtime: "claude-code" });
let elapsed = 0, requested = false, packetPasted = false;
const headerless = mode.startsWith("working-headerless-auto");
if (headerless) new NativePermissionStore(db).write(node.id, { runtime: "claude-code", mode: "auto" }, "fixture", "headerless auto qualification");
const nativeAt = mode === "new-runtime-already-ready" || mode === "working-wrapper-screen-unavailable" || headerless ? 0 : 500;
const unavailable = mode.endsWith("screen-unavailable");
let currentScreenInput = false;
const identicalFrame = "Claude Code v2.1.303\n❯ \n ⏵⏵ accept edits on (shift+tab to cycle)";
const autoFrame = "Restored conversation\n─────────\n❯ \n─────────\n ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents";
const oldFrame = "Claude Code v2.1.303\n❯ predecessor finished turn\n ⏵⏵ accept edits on (shift+tab to cycle)";
const freshFrame = "Claude Code v2.1.303\n❯ \n ⏵⏵ accept edits on (shift+tab to cycle)";
const trace: unknown[] = [];
const pane = () => headerless ? identicalFrame + "\n" + autoFrame : unavailable ? identicalFrame : requested && elapsed >= nativeAt ? freshFrame
: mode === "classic-predecessor-history" ? oldFrame + "\noperator@host$ " : "operator@host$ ";
getPaneCommand.mockImplementation(async () => (unavailable || headerless) && requested ? "sh" : requested && elapsed >= nativeAt ? "claude" : "zsh");
capturePaneScreen.mockImplementation(async () => unavailable ? null : headerless ? autoFrame : requested && elapsed < nativeAt ? "operator@host$ " : pane());
const capturePaneContent = vi.fn(async (_session: string, lines: number) => {
const returnedPane = currentScreenInput ? (unavailable ? null : headerless ? autoFrame : requested && elapsed < nativeAt ? "operator@host$ " : pane()) : pane();
trace.push({ kind: "read", reader: currentScreenInput ? "current-screen-input" : "40-line-input", elapsed, lines,
successorRuntimeStarted: requested && elapsed >= nativeAt, returnedPane });
return returnedPane;
});
const target = { ...tmux(), capturePaneContent, getPanePid: vi.fn(async () => 100),
sendShellCommand: async (session: string, text: string) => sendText(session, text) } as unknown as TmuxAdapter;
const fsError = () => { throw new Error("unexpected filesystem access"); };
const listProcesses = vi.fn(() => {
if (unavailable || mode === "working-headerless-auto-unavailable") throw new Error("fixture ps unavailable");
if (mode !== "working-headerless-auto-proof") return [];
const startedAt = "Mon Oct 19 21:30:00 2026";
return [
{ pid: 100, ppid: 1, pgid: 100, tpgid: 101, command: "-bash", executableName: "bash", startedAt },
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, command: "/bin/sh fixture-launch.txt", executableName: "sh", startedAt },
{ pid: 102, ppid: 101, pgid: 101, tpgid: 101, command: `/opt/claude.exe --permission-mode auto --session-id ${successorToken} --name dev-impl@seat-rig`, executableName: "claude", startedAt },
];
});
const claude = new ClaudeCodeAdapter({ tmux: target, fsOps: { exists: () => false, readFile: fsError,
writeFile: fsError, mkdirp: fsError, copyFile: fsError }, listProcesses, sleep: async () => {}, sessionIdFactory: () => successorToken,
...(headerless ? { claudeManagedLaunch: { prepare: async () => ({ command: (args: readonly string[]) => `claude ${args.join(" ")}`,
assertCurrent: () => {}, configDir: "/fixture" }) } as never } : {}) });
const reads: unknown[] = [];
const readiness = vi.fn(async (binding) => {
const result = await claude.checkReady(binding);
// Counterfactual reader qualification only: same public method with visible-screen bytes,
// no production source edit, no fresh-launch refusal shipped.
currentScreenInput = true;
const withCurrentScreen = await claude.checkReady(binding);
currentScreenInput = false;
reads.push({ elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, result, withCurrentScreen });
return result;
});
const successorToken = "22222222-2222-4222-8222-222222222222";
sendText.mockImplementation(async (session: string, text: string) => {
if (text.includes("claude ") && text.includes("--session-id")) {
requested = true; trace.push({ kind: "launch-text", elapsed, session, text, nativeAt });
return { ok: true };
}
packetPasted = true;
trace.push({ kind: "packet-paste", session, elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, text });
return { ok: true };
});
sendKeys.mockImplementation(async (session: string, keys: string[]) => {
trace.push({ kind: packetPasted ? "input" : "launch-input", session, elapsed, successorRuntimeStarted: requested && elapsed >= nativeAt, keys });
return { ok: true };
});
const handover = new SeatHandoverService({ db, rigRepo, sessionRegistry, discoveryRepo, eventBus,
tmuxAdapter: target, now: () => new Date("2026-10-19T21:30:00Z"), newSuccessorId: () => "01SUCCID0",
runtimeAdapters: { "claude-code": { runtime: "claude-code", launchHarness: claude.launchHarness.bind(claude), checkReady: readiness } as unknown as RuntimeAdapter },
contextUsageStore: { readSidecar } as never, resumeTokenCapturer: { captureCodexThreadId } as never,
occupantInvalidator: { invalidateRetiringOccupant }, activityOracle: { declareOccupantSwap },
predecessorRecapResolver: resolvePredecessorRecap as never, readinessTimeoutMs: 5000,
sleep: async ms => { elapsed += ms; trace.push({ kind: "sleep", ms, elapsed }); },
});
const result = await handover.handover({ seatRef: "dev-impl@seat-rig", source: "fresh", reason: "bounded scrollback reproduction" });
const sessions = db.prepare("SELECT session_name,status,resume_token,startup_status FROM sessions ORDER BY id").all();
const oldProbe = assessNativeResumeProbe({ runtime: "claude-code", paneCommand: "zsh", paneContent: oldFrame + "\noperator@host$ " });
observations.push({ mode, result, trace, reads, listProcessesCalls: listProcesses.mock.calls.length,
sessions, oldProbe, respawn: respawnPane.mock.calls });
expect(result.ok).toBe(true);
expect(reads.length).toBeGreaterThan(0);
const first = reads[0] as any;
if (mode === "classic-predecessor-history") {
expect(first.result.ready).toBe(true);
expect(first.successorRuntimeStarted).toBe(false);
expect(first.withCurrentScreen.ready).toBe(false);
} else if (headerless) {
const launched = trace.find((x: any) => x.kind === "launch-text") as any;
expect(launched.text).toMatch(/--permission-mode auto(?: |$)/);
expect(first.result.ready).toBe(true);
expect(first.successorRuntimeStarted).toBe(true);
expect(first.withCurrentScreen.ready).toBe(mode === "working-headerless-auto-proof");
if (mode === "working-headerless-auto-unavailable") expect(first.withCurrentScreen.code).toBe("claude_auto_identity_required");
} else if (unavailable) {
expect(first.result.ready).toBe(true);
expect(first.withCurrentScreen).toMatchObject({ ready: false, code: "awaiting_runtime" });
expect(first.successorRuntimeStarted).toBe(mode === "working-wrapper-screen-unavailable");
} else if (mode === "new-runtime-already-ready") expect(first.withCurrentScreen.ready).toBe(true);
else {
expect(first.result.ready).toBe(false);
expect((reads.at(-1) as any).withCurrentScreen.ready).toBe(true);
}
});
});
afterAll(() => {
const dir = process.env.CHECKPOINT_OUTPUT;
if (!dir) throw new Error("diagnostic output directory required");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "observations.json"), JSON.stringify(observations, null, 2) + "\n");
});