import { spawn, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'

import yaml from 'js-yaml'
import { expect, test } from 'vitest'

import { candidateSmokeHermesHomes, predictSmokeHermesHome, resolveSmokeLaunch, runInstalledDesktopSmoke, smokeEnvironment } from '../../tests/install/e2e-assets/desktop-smoke.ts'
import { sourceRuntimeSettleCommand } from '../../tests/install/e2e-assets/source-runtime-settle.mjs'
import { assertUpdateWindowBackendOrigin, assertUpdateWindowProcess } from '../../tests/install/e2e-assets/update-window-chat.mjs'

import { assertChatCommit, newCompletedPair, readMockPrompts, type TranscriptMessage } from './desktop-chat-smoke.ts'
import { assertBackendOrigin, localBackendProcess, readBundledBundleEnv, readInstallationCommit } from './desktop-smoke-process.ts'
import { writeEnvFile, writeMockProviderConfig } from './mock-provider-config.ts'
import { MOCK_REPLY, startMockServer } from './mock-server.ts'

test('a later CI step\'s mock rebinds the dead mock\'s port, so a gateway that outlived it still answers', async (): Promise<void> => {
  // The detached gateway froze `http://127.0.0.1:<port>` at boot; the step that started that mock is gone.
  const earlier = await startMockServer()
  await earlier.close()
  const later = await startMockServer({ port: earlier.port })

  try {
    expect(later.url).toBe(earlier.url)
  } finally { await later.close() }
})

test('one server owns inference and a fresh, per-server prompt witness', async (): Promise<void> => {
  const first = await startMockServer()
  const second = await startMockServer()

  try {
    expect(await readMockPrompts(first.url)).toEqual([])
    const prompt = 'unique request from the OLD checkpoint'

    const response = await fetch(`${first.url}/v1/chat/completions`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ model: 'mock-model', messages: [{ role: 'user', content: prompt }] }),
    })

    expect(await response.json()).toMatchObject({ choices: [{ message: { content: MOCK_REPLY } }] })
    expect(await readMockPrompts(first.url)).toEqual(first.receivedPrompts)
    expect(first.receivedPrompts).toEqual([prompt])
    await fetch(`${first.url}/v1/chat/completions`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ messages: [{ role: 'user', content: [{ type: 'text', text: 'NEW multipart prompt' }] }] }),
    })
    expect(await readMockPrompts(first.url)).toEqual([prompt, 'NEW multipart prompt'])

    // A body shape the witness does not recognize records nothing rather than a
    // half-parsed string — that is the empty-witness case the desktop smoke
    // reports as a poll timeout, so the mock logs the parsed shape on this path.
    for (const content of [{ type: 'text', text: 'unrecognized shape' }, 42]) {
      await fetch(`${first.url}/v1/chat/completions`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ messages: [{ role: 'user', content }] }),
      })
    }

    // No user turn at all leaves `lastUserMessage` undefined — the shape the
    // diagnostic must still describe without throwing.
    await fetch(`${first.url}/v1/chat/completions`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ messages: [{ role: 'assistant', content: 'no user turn' }] }),
    })
    expect(await readMockPrompts(first.url)).toEqual([prompt, 'NEW multipart prompt'])
    expect(await readMockPrompts(second.url)).toEqual([])
  } finally {
    await first.close()
    await second.close()
  }

  await expect(readMockPrompts(first.url)).rejects.toThrow()
})

test('provider reconfiguration preserves feed, plugins, history and explicit fixture options', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-config-'))

  try {
    const feed = { desktop_feed_base_url: 'http://127.0.0.1:8123/feed' }
    fs.writeFileSync(path.join(home, 'config.yaml'), yaml.dump({ updates: feed, plugins: { witness: true }, model: { temperature: 0.7 } }))
    fs.writeFileSync(path.join(home, 'state.db'), 'history witness')
    fs.writeFileSync(path.join(home, '.env'), 'OTHER_TEST_VALUE=kept\nMOCK_API_KEY=old\n')
    writeMockProviderConfig(home, 'http://127.0.0.1:9000')
    writeEnvFile(home)
    writeMockProviderConfig(home, 'http://127.0.0.1:9001', 'interim_assistant_messages: true', 'approvals:\n  mode: smart', 12000)
    writeEnvFile(home)
    expect(yaml.load(fs.readFileSync(path.join(home, 'config.yaml'), 'utf8'))).toMatchObject({
      updates: feed, plugins: { witness: true },
      model: { provider: 'custom', base_url: 'http://127.0.0.1:9001/v1', temperature: 0.7, context_length: 12000 },
      providers: {},
      // Exactly one entry, repointed at the second URL: reconfiguring must not
      // stack duplicates. The readiness ladder reads model.base_url (above),
      // not this list; the entry names the same endpoint for `custom:<name>`.
      custom_providers: [{ name: 'Mock', base_url: 'http://127.0.0.1:9001/v1', key_env: 'OPENAI_API_KEY' }],
      auxiliary: { title_generation: { enabled: false } }, approvals: { mode: 'smart' }, display: { interim_assistant_messages: true },
      onboarding: { run: false },
    })
    expect(fs.readFileSync(path.join(home, '.env'), 'utf8')).toBe('OTHER_TEST_VALUE=kept\nMOCK_API_KEY=e2e-mock-key\n')
    expect(fs.readFileSync(path.join(home, 'state.db'), 'utf8')).toBe('history witness')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test('OLD history, partial/wrong replies, active streams and errors cannot satisfy NEW', (): void => {
  const message = (id: string, role: string, text: string): TranscriptMessage => ({ id, role, text, streaming: false, error: false })
  const old = [message('old-user', 'user', 'old'), message('old-reply', 'assistant', MOCK_REPLY)]
  const before = old.map((row: TranscriptMessage): string => row.id)
  const user = message('new-user', 'user', 'new nonce')
  const reply = message('new-reply', 'assistant', MOCK_REPLY)

  for (const rows of [old, [...old, user], [...old, user, { ...reply, id: 'old-reply' }],
    [...old, user, { ...reply, text: 'boot chain is working' }], [...old, user, { ...reply, text: 'wrong' }],
    [...old, user, { ...reply, streaming: true }], [...old, user, { ...reply, error: true }],
    [...old, user, message('other-user', 'user', 'unrelated'), reply]]) {
    expect(newCompletedPair(rows, before, 'new nonce')).toBeNull()
  }

  expect(newCompletedPair([...old, user, reply], before, 'new nonce')).toEqual({ user, assistant: reply })
})

test.runIf(process.platform !== 'win32')('shell wrapper exports the live witness URL without losing journey config', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-wrapper-'))
  const assets = path.resolve(import.meta.dirname, '../../tests/install/e2e-assets')

  try {
    fs.writeFileSync(path.join(home, 'config.yaml'), 'updates:\n  desktop_feed_base_url: http://127.0.0.1:1234/feed\n')

    const result = spawnSync('bash', ['-c', `
      set -eu
      ok() { printf '%s\\n' "$*"; }
      fail() { printf '%s\\n' "$*" >&2; exit 1; }
      log_group() { :; }
      source "$ASSETS/mock-provider.sh"
      trap mock_stop EXIT
      mock_start "$HERMES_HOME"
      node --input-type=module -e 'const r=await fetch(process.env.HERMES_E2E_MOCK_URL+"/__e2e__/prompts"); if(!r.ok || (await r.json()).receivedPrompts.length!==0)process.exit(1)'
    `], { env: { ...process.env, ASSETS: assets, HERMES_HOME: home, LOG_DIR: home }, encoding: 'utf8', timeout: 20_000 })

    expect(result.status, result.stdout + result.stderr).toBe(0)
    expect(yaml.load(fs.readFileSync(path.join(home, 'config.yaml'), 'utf8'))).toMatchObject({ updates: { desktop_feed_base_url: 'http://127.0.0.1:1234/feed' } })
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test.runIf(process.platform === 'linux')('origin proof finds the live child listener and rejects a package impostor', async (): Promise<void> => {
  const child = spawn(process.execPath, ['-e', 'const s=require("node:net").createServer();s.listen(0,"127.0.0.1",()=>console.log(s.address().port))'], { stdio: ['ignore', 'pipe', 'pipe'] })

  try {
    const port = await new Promise<number>((resolve, reject): void => {
      child.once('error', reject)
      child.stdout.once('data', (data: Buffer): void => resolve(Number(data.toString().trim())))
    })

    const backend = localBackendProcess(port, process.pid)
    expect(backend.pid).toBe(child.pid)
    expect(backend.executable).toBe(fs.realpathSync(process.execPath))
    expect((): void => assertBackendOrigin(backend, os.tmpdir(), 'bundled')).toThrow('agent-payload')
    expect((): void => assertBackendOrigin(backend, os.tmpdir(), 'source')).toThrow('source tree')
    expect((): void => { localBackendProcess(port, child.pid!) }).toThrow('found 0')
  } finally { child.kill(); await new Promise<void>((resolve): void => { child.once('exit', (): void => resolve()) }) }
})

test.runIf(process.platform === 'linux' || process.platform === 'darwin')('a shared host listener is proven by this home ledger, not Electron ancestry', async (): Promise<void> => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-host-listener-'))
  const child = spawn(process.execPath, ['-e', 'const s=require("node:net").createServer();s.listen(0,"127.0.0.1",()=>console.log(s.address().port))'], { stdio: ['ignore', 'pipe', 'pipe'] })

  try {
    const port = await new Promise<number>((resolve, reject): void => {
      child.once('error', reject)
      child.stdout.once('data', (data: Buffer): void => resolve(Number(data.toString().trim())))
    })

    // A reused host backend is not a descendant of the newly opened Desktop.
    // Here the real listener itself is outside that descendant set.
    const ledger = path.join(home, 'spawn-ledger.json')
    const record = { pid: child.pid, purpose: 'serve', host: '127.0.0.1', port }
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')

    for (const invalid of [{ ...record, port: port + 1 }, { ...record, pid: process.pid },
      { ...record, isolated: true }, { ...record, purpose: 'update' }]) {
      fs.writeFileSync(ledger, JSON.stringify([invalid]))
      expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
    }

    fs.writeFileSync(ledger, JSON.stringify([record]))
    const backend = localBackendProcess(port, child.pid!, home)
    expect(backend.pid).toBe(child.pid)
    expect(backend.ownership).toBe('host-ledger')
    // Registration does not waive the installed-tree provenance check.
    expect(() => assertBackendOrigin(backend, home, 'bundled')).toThrow('agent-payload')
    fs.writeFileSync(ledger, 'invalid')
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
  } finally {
    child.kill()
    await new Promise<void>((resolve): void => { child.once('exit', (): void => resolve()) })
    fs.rmSync(home, { recursive: true, force: true })
  }
})

test.runIf(process.platform === 'linux' || process.platform === 'darwin')('a host ledger record proves only its own incarnation, read at the machine root', async (): Promise<void> => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-host-root-'))
  // A profile home: the product writes the ledger at the machine root above it.
  const home = path.join(root, 'profiles', 'work')
  fs.mkdirSync(home, { recursive: true })
  const spawnedAt = Date.now() / 1000
  const child = spawn(process.execPath, ['-e', 'const s=require("node:net").createServer();s.listen(0,"127.0.0.1",()=>console.log(s.address().port))'], { stdio: ['ignore', 'pipe', 'pipe'] })

  try {
    const port = await new Promise<number>((resolve, reject): void => {
      child.once('error', reject)
      child.stdout.once('data', (data: Buffer): void => resolve(Number(data.toString().trim())))
    })

    const record = { pid: child.pid, purpose: 'serve', host: '127.0.0.1', port, create_time: spawnedAt }
    // The profile directory is not where the product writes the ledger.
    fs.writeFileSync(path.join(home, 'spawn-ledger.json'), JSON.stringify([record]))
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
    fs.rmSync(path.join(home, 'spawn-ledger.json'))
    const ledger = path.join(root, 'spawn-ledger.json')
    // A stale record whose PID now names a different process (reused PID) is no evidence.
    fs.writeFileSync(ledger, JSON.stringify([{ ...record, create_time: spawnedAt - 3600 }]))
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
    fs.writeFileSync(ledger, JSON.stringify([record]))
    expect(localBackendProcess(port, child.pid!, home).ownership).toBe('host-ledger')
  } finally {
    child.kill()
    await new Promise<void>((resolve): void => { child.once('exit', (): void => resolve()) })
    fs.rmSync(root, { recursive: true, force: true })
  }
})

test.runIf(process.platform === 'linux' || process.platform === 'darwin')('the canonical gateway gateway_state.json names is the listener, for its own incarnation only', async (): Promise<void> => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-host-gateway-'))
  // A named profile: the root's multiplexer serves it and records its own state there.
  const home = path.join(root, 'profiles', 'work')
  fs.mkdirSync(home, { recursive: true })
  const spawnedAt = Date.now() / 1000
  const child = spawn(process.execPath, ['-e', 'const s=require("node:net").createServer();s.listen(0,"127.0.0.1",()=>console.log(s.address().port))'], { stdio: ['ignore', 'pipe', 'pipe'] })

  try {
    const port = await new Promise<number>((resolve, reject): void => {
      child.once('error', reject)
      child.stdout.once('data', (data: Buffer): void => resolve(Number(data.toString().trim())))
    })

    // gateway.status._get_process_start_time: raw start ticks on Linux, centiseconds elsewhere.
    const startTime = process.platform === 'linux'
      ? Number(fs.readFileSync(`/proc/${child.pid}/stat`, 'utf8').split(')').pop()!.trim().split(/\s+/)[19])
      : Math.round(spawnedAt * 100)

    const state = { pid: child.pid, kind: 'hermes-gateway', gateway_state: 'running', start_time: startTime }
    const statePath = path.join(root, 'gateway_state.json')
    // `gateway ensure` leaves the daemon outside the app's tree and out of the spawn ledger.
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')

    for (const invalid of [{ ...state, gateway_state: 'stopped' }, { ...state, kind: 'other' },
      { ...state, pid: process.pid }, { ...state, start_time: startTime + 360_000 }]) {
      fs.writeFileSync(statePath, JSON.stringify(invalid))
      expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
    }

    fs.writeFileSync(statePath, JSON.stringify(state))
    const backend = localBackendProcess(port, child.pid!, home)
    expect(backend.pid).toBe(child.pid)
    expect(backend.ownership).toBe('host-gateway')
    // The gateway is not tied to the app by ancestry: its own tree evidence is still required.
    expect(() => assertBackendOrigin(backend, root, 'source', { appReportedRoot: root })).toThrow('source tree')
    fs.writeFileSync(statePath, 'invalid')
    expect(() => localBackendProcess(port, child.pid!, home)).toThrow('found 0')
  } finally {
    child.kill()
    await new Promise<void>((resolve): void => { child.once('exit', (): void => resolve()) })
    fs.rmSync(root, { recursive: true, force: true })
  }
})

test('a canonical gateway proves its tree by the cwd `gateway ensure` starts it in', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-gateway-origin-'))

  try {
    // spawn_unmanaged_gateway: `sys.executable -m hermes_cli.main --profile default gateway run`, cwd=<install root>.
    // The interpreter is the managed one beside the checkout, so argv never names the root.
    const executable = path.join(os.tmpdir(), 'hermes-tools', 'python', 'bin', 'python3.14')

    const backend = { pid: 2, parentPid: 1, executable, cwd: root, ownership: 'host-gateway' as const,
      command: [executable, '-m', 'hermes_cli.main', '--profile', 'default', 'gateway', 'run', '--quiet'].map(arg => JSON.stringify(arg)).join(' ') }

    expect(() => assertBackendOrigin(backend, root, 'source', { appReportedRoot: root })).not.toThrow()
    // The app's own report never vouches for a listener that is not its descendant.
    expect(() => assertBackendOrigin({ ...backend, cwd: os.tmpdir() }, root, 'source', { appReportedRoot: root })).toThrow('different source tree')
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('a canonical gateway whose cwd is unreadable proves its tree by the commit it stamped', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-gateway-sha-'))

  try {
    // Windows: CIM exposes neither cwd nor environment for the detached `gateway run`.
    const executable = path.join(os.tmpdir(), 'hermes-tools', 'python', 'python.exe')
    const commit = 'a'.repeat(40)

    const backend = { pid: 2, parentPid: 1, executable, ownership: 'host-gateway' as const, codeSha: commit,
      command: [executable, '-m', 'hermes_cli.main', '--profile', 'default', 'gateway', 'run', '--quiet'].map(arg => JSON.stringify(arg)).join(' ') }

    expect(() => assertBackendOrigin(backend, root, 'source', { appReportedRoot: root, installedCommit: commit })).not.toThrow()
    // A gateway started from another commit (or one that stamped none) is not vouched for.
    expect(() => assertBackendOrigin({ ...backend, codeSha: 'b'.repeat(40) }, root, 'source', { appReportedRoot: root, installedCommit: commit })).toThrow('different source tree')
    expect(() => assertBackendOrigin({ ...backend, codeSha: undefined }, root, 'source', { appReportedRoot: root, installedCommit: commit })).toThrow('different source tree')
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('a backend bound to the tree by environment needs no root in argv', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-env-origin-'))

  try {
    const root = path.join(home, 'hermes-agent')
    fs.mkdirSync(path.join(root, 'venv'), { recursive: true })
    // The macOS shape: a venv's interpreter is a SYMLINK to the framework
    // binary, and the app resolves it before spawning, so argv names that binary
    // and never the installation root.
    const resolvedPython = fs.realpathSync(process.execPath)
    const interpreter = path.join(home, 'python3.11')
    fs.symlinkSync(resolvedPython, interpreter)

    const base = { pid: process.pid, parentPid: 1, executable: interpreter, cwd: home, ownership: 'child' as const,
      command: `"${resolvedPython}" "-m" "hermes_cli.main" serve --host 127.0.0.1 --port 0` }

    // Control: with no environment evidence this is still a different tree.
    expect((): void => { assertBackendOrigin(base, root, 'source') }).toThrow('source tree')
    expect((): void => {
      assertBackendOrigin({ ...base, pythonPath: `${root}${path.delimiter}/elsewhere` }, root, 'source')
    }).not.toThrow()
    expect((): void => {
      assertBackendOrigin({ ...base, virtualEnv: path.join(root, 'venv') }, root, 'source')
    }).not.toThrow()
    // Evidence naming some OTHER tree is no evidence for this one.
    expect((): void => {
      assertBackendOrigin({ ...base, pythonPath: path.join(home, 'elsewhere') }, root, 'source')
    }).toThrow('source tree')
    expect((): void => {
      assertBackendOrigin({ ...base, virtualEnv: path.join(home, 'other-venv') }, root, 'source')
    }).toThrow('source tree')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test('a platform that cannot read the backend environment proves ownership by the app', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-owner-origin-'))

  try {
    const root = path.join(home, 'hermes-agent')
    const other = path.join(home, 'other-tree')
    fs.mkdirSync(root, { recursive: true })
    fs.mkdirSync(other, { recursive: true })

    // The Windows shape: the app's venv launcher hands the interpreter over as a system
    // python, so argv never names the tree, and this platform exposes neither the
    // backend's cwd nor its environment.
    const backend = {
      pid: process.pid,
      parentPid: 1,
      executable: path.join(home, 'python.exe'),
      command: `"${path.join(home, 'python.exe')}" "-m" "hermes_cli.main" serve --host 127.0.0.1 --port 0`,
      ownership: 'child' as const,
    }

    // Control row: with nothing readable and no report from the app, this is still a
    // different tree.
    expect((): void => { assertBackendOrigin(backend, root, 'source') }).toThrow('source tree')
    // The app reported resolving this root (the driver asserts that against options.root
    // before calling), and the listener was already tied to that same app process.
    expect((): void => {
      assertBackendOrigin(backend, root, 'source', { appReportedRoot: root })
    }).not.toThrow()
    // A report of some other tree is no evidence for this one.
    expect((): void => {
      assertBackendOrigin(backend, root, 'source', { appReportedRoot: other })
    }).toThrow('source tree')
    // Readable process evidence keeps the strict path: an environment naming another
    // tree is not rescued by the app's own report.
    expect((): void => {
      assertBackendOrigin({ ...backend, cwd: other, pythonPath: other }, root, 'source', { appReportedRoot: root })
    }).toThrow('source tree')
    // A ledger-registered host backend is not tied to this app by ancestry, so the
    // app's report is no evidence for it: it needs its own argv/env evidence.
    expect((): void => {
      assertBackendOrigin({ ...backend, ownership: 'host-ledger' }, root, 'source', { appReportedRoot: root })
    }).toThrow('source tree')
    expect((): void => {
      assertBackendOrigin({ ...backend, ownership: 'child' }, root, 'source', { appReportedRoot: root })
    }).not.toThrow()
    // Ancestry is the waiver's premise, so it must be stated, not inferred from an
    // absent field: a backend whose ownership was never established gets no waiver.
    expect((): void => {
      assertBackendOrigin({ ...backend, ownership: undefined } as unknown as typeof backend, root, 'source', { appReportedRoot: root })
    }).toThrow('source tree')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test('OLD update-window source provenance carries its verified app identity to the listener check', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-old-owner-origin-'))

  try {
    const root = path.join(home, 'hermes-agent')
    const other = path.join(home, 'other-tree')
    fs.mkdirSync(root, { recursive: true })
    fs.mkdirSync(other, { recursive: true })

    // As `localBackendProcess` reports a listener that descends from the app.
    const backend = { pid: 2, parentPid: 1, executable: path.join(home, 'python.exe'), ownership: 'child' as const,
      command: `"${path.join(home, 'python.exe')}" -m hermes_cli.main dashboard --port 0` }

    expect((): void => {
      assertUpdateWindowBackendOrigin(backend, { hermesRoot: root }, root, 'source')
    }).not.toThrow()
    expect((): void => {
      assertUpdateWindowBackendOrigin(backend, { hermesRoot: other }, root, 'source')
    }).toThrow('resolved another source installation')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test('source launch restores only an explicitly captured exact editable root', (): void => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-source-'))
  const specPath = path.join(home, 'launch.json')
  const childRoot = path.join(home, 'child')
  fs.mkdirSync(childRoot)

  const options = { exe: process.execPath, root: home, origin: 'source' as const, home,
    'user-data': path.join(home, 'user-data'), out: home, phase: 'old' as const,
    'expect-commit': 'a'.repeat(40), 'launch-spec': specPath }

  const writeSpec = (sourceRoot: string): void => {
    // oxlint-disable-next-line anti-slop/no-shape-in-symbol-names -- This is the existing launch-capture wire field.
    fs.writeFileSync(specPath, JSON.stringify({ argv: [process.execPath], cwd: home, matchedShape: 'packaged',
      env: { HERMES_DESKTOP_PYTHON: process.execPath, HERMES_PYTHON_SRC_ROOT: sourceRoot } }))
  }

  try {
    writeSpec(home)
    expect(resolveSmokeLaunch(options).env.HERMES_PYTHON_SRC_ROOT).toBe(home)

    for (const wrong of [childRoot, path.dirname(home), 'relative-root']) {
      writeSpec(wrong)
      expect((): void => { resolveSmokeLaunch(options) }).toThrow('expected source installation')
    }

    expect(smokeEnvironment({ HERMES_PYTHON_SRC_ROOT: home }, home, options['user-data']).HERMES_PYTHON_SRC_ROOT).toBeUndefined()
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test.runIf(process.platform === 'linux')('module-launched source listener proves its import root without an argv path', async (): Promise<void> => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-module-'))
  fs.mkdirSync(path.join(home, 'hermes_cli'))
  fs.writeFileSync(path.join(home, 'hermes_cli', '__init__.py'), '')
  fs.writeFileSync(path.join(home, 'hermes_cli', 'main.py'), 'import socket, time\ns = socket.socket()\ns.bind(("127.0.0.1", 0))\ns.listen()\nprint(s.getsockname()[1], flush=True)\ntime.sleep(60)\n')

  const child = spawn('python3', ['-m', 'hermes_cli.main'], {
    cwd: home, env: { ...process.env, HERMES_PYTHON_SRC_ROOT: home }, stdio: ['ignore', 'pipe', 'pipe'],
  })

  try {
    const port = await new Promise<number>((resolve, reject): void => {
      child.once('error', reject)
      child.stdout.once('data', (data: Buffer): void => resolve(Number(data.toString().trim())))
    })

    const backend = localBackendProcess(port, process.pid)
    expect(backend.sourceRoot).toBe(home)
    expect(backend.cwd).toBe(home)
    expect((): void => assertBackendOrigin(backend, home, 'source')).not.toThrow()
    expect((): void => assertBackendOrigin({ ...backend, sourceRoot: undefined }, home, 'source')).not.toThrow()
    expect((): void => assertBackendOrigin(backend, os.tmpdir(), 'source')).toThrow('source tree')
  } finally {
    child.kill()
    await new Promise<void>((resolve): void => { child.once('exit', (): void => resolve()) })
    fs.rmSync(home, { recursive: true, force: true })
  }
})

test('a module launch proves its tree without leaning on the app-owned cwd', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-venv-root-'))

  try {
    const python = path.join(root, 'venv', 'bin', 'python')

    const launched = (executable: string, command: string): Parameters<typeof assertBackendOrigin>[0] =>
      ({ pid: 1, parentPid: 1, executable, command, cwd: path.join(os.tmpdir(), 'app-owned-cwd'), ownership: 'child' })

    const venv = launched(python, `"${python}" "-m" "hermes_cli.main" "serve" --host 127.0.0.1 --port 0`)
    // The app owns the backend's cwd; the installation's own venv interpreter is the evidence.
    expect((): void => assertBackendOrigin(venv, root, 'source')).not.toThrow()
    // A foreign interpreter whose command names no tree is still rejected.
    expect((): void => assertBackendOrigin(launched('/usr/bin/python3', '"python3" "-m" "hermes_cli.main" "serve"'), root, 'source')).toThrow('source tree')
    // A captured root disagrees: authoritative, even when the command names the expected tree.
    expect((): void => assertBackendOrigin({ ...venv, sourceRoot: os.tmpdir() }, root, 'source')).toThrow('source tree')
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('historical identity needs verified provenance and never overrides an app-reported mismatch', (): void => {
  const expected = 'a'.repeat(40)
  const identity = { appVersion: 'historical', commit: null, hermesRoot: '/unused', platform: process.platform }
  expect((): void => assertChatCommit(identity, expected)).toThrow('provenance is required')
  expect((): void => assertChatCommit(identity, expected, expected)).not.toThrow()
  expect((): void => assertChatCommit(identity, expected, 'b'.repeat(40))).toThrow('does not equal expected')
  expect((): void => assertChatCommit({ ...identity, commit: 'b'.repeat(40) }, expected, expected)).toThrow('Running commit')
  expect((): void => assertChatCommit({ ...identity, commit: expected }, expected)).not.toThrow()
  const resources = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-stamp-'))
  const payload = path.join(resources, 'agent-payload')
  fs.mkdirSync(payload)

  try {
    fs.writeFileSync(path.join(resources, 'install-stamp.json'), JSON.stringify({ payload: 'bundled', commit: expected }))
    expect(readInstallationCommit(payload, 'bundled')).toBe(expected)
    fs.writeFileSync(path.join(resources, 'install-stamp.json'), JSON.stringify({ payload: 'bundled', commit: 'malformed' }))
    expect((): void => { readInstallationCommit(payload, 'bundled') }).toThrow()
  } finally { fs.rmSync(resources, { recursive: true, force: true }) }
})

test('driver strips caller secrets and records missing executables as failure without launching', async (): Promise<void> => {
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-admission-'))

  try {
    const env = smokeEnvironment({ PATH: '/usr/bin', DISPLAY: ':1', OPENAI_API_KEY: 'secret', HERMES_DESKTOP_BOOT_FAKE: '1',
      HERMES_DESKTOP_HERMES_ROOT: '/wrong', PYTHONPATH: '/wrong', NODE_OPTIONS: '--inspect', HERMES_HOME: '/wrong' }, home, path.join(home, 'user-data'))

    expect(env).toMatchObject({ PATH: '/usr/bin', DISPLAY: ':1', HERMES_HOME: home,
      HERMES_DESKTOP_SKIP_QUIT_CONFIRM: '1' })

    for (const key of ['OPENAI_API_KEY', 'HERMES_DESKTOP_BOOT_FAKE', 'HERMES_DESKTOP_HERMES_ROOT', 'PYTHONPATH', 'NODE_OPTIONS']) {
      expect(env[key]).toBeUndefined()
    }

    await expect(runInstalledDesktopSmoke({ exe: path.join(home, 'missing'), root: home, origin: 'bundled', home,
      'user-data': path.join(home, 'user-data'), out: home, phase: 'installed', 'expect-commit': 'a'.repeat(40) })).rejects.toThrow()
    expect(JSON.parse(fs.readFileSync(path.join(home, 'desktop-chat-installed.json'), 'utf8'))).toMatchObject({ status: 'failed', origin: 'bundled' })
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test.runIf(process.platform !== 'win32')('OLD and NEW source smokes settle the clean runtime before Electron launch', async (): Promise<void> => {
  for (const phase of ['old', 'new'] as const) {
  const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-source-settle-'))
  const root = path.join(workspace, 'source')
  const home = path.join(workspace, 'home')
  const out = path.join(workspace, 'out')
  const userData = path.join(workspace, 'user-data')
  const exe = path.join(root, 'fake-desktop')
  const launcher = path.join(root, '.hermes', 'bin', 'hermes')
  const witness = path.join(workspace, 'settled')

  try {
    fs.mkdirSync(path.dirname(launcher), { recursive: true })
    fs.writeFileSync(exe, '#!/bin/sh\nexit 1\n')
    fs.chmodSync(exe, 0o755)
    fs.writeFileSync(launcher, `#!/bin/sh
set -eu
[ "$1" = status ]
[ "$HERMES_HOME" = ${JSON.stringify(home)} ]
[ "$HOME" = ${JSON.stringify(path.join(home, '.desktop-smoke-home'))} ]
[ -z "\${PM_E2E_LEAK-}" ]
printf 'clean source runtime settled\\n'
: > ${JSON.stringify(witness)}
`)
    fs.chmodSync(launcher, 0o755)

    const refuseLaunch = async (): Promise<never> => {
      expect(fs.existsSync(witness)).toBe(true)
      throw new Error('launch observed settled runtime')
    }

    const prior = process.env.PM_E2E_LEAK
    process.env.PM_E2E_LEAK = 'must be stripped'

    try {
      await expect(runInstalledDesktopSmoke({ exe, root, origin: 'source', home, 'user-data': userData,
        out, phase, 'expect-commit': 'a'.repeat(40) }, refuseLaunch)).rejects.toThrow('launch observed settled runtime')
    } finally {
      if (prior === undefined) { delete process.env.PM_E2E_LEAK } else { process.env.PM_E2E_LEAK = prior }
    }

    expect(fs.readFileSync(path.join(out, `desktop-source-settle-${phase}.log`), 'utf8')).toContain('clean source runtime settled')
  } finally { fs.rmSync(workspace, { recursive: true, force: true }) }
  }
})

test('Windows source settle bypasses the current cmd launcher beside a stale historical exe', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-windows-settle-'))

  try {
    const bin = path.join(root, '.hermes', 'bin')
    fs.mkdirSync(bin, { recursive: true })
    const current = path.join(bin, 'hermes.cmd')
    const python = path.join(root, 'managed python', 'python.exe')
    fs.mkdirSync(path.dirname(python), { recursive: true })
    fs.writeFileSync(python, '')
    const prepareLaunch = path.join(root, 'hermes_cli', 'venv_sync.py')
    fs.mkdirSync(path.dirname(prepareLaunch), { recursive: true })
    fs.writeFileSync(prepareLaunch, '')
    fs.writeFileSync(current, `@"${python}" -I -c "import base64; exec(base64.b64decode('eA=='))" %*\r\n`)
    fs.writeFileSync(path.join(bin, 'hermes.exe'), 'locked historical launcher')
    const invocation = sourceRuntimeSettleCommand(root, { ComSpec: 'C:\\Windows\\System32\\cmd.exe' }, 'win32')
    expect(invocation).toEqual({
      launcher: current,
      command: python,
      args: ['-I', '-B', '-c', `import pathlib, sys; sys.path.insert(0, ${JSON.stringify(root)}); from hermes_cli.venv_sync import prepare_launch; prepare_launch(pathlib.Path(${JSON.stringify(root)}), ['status'])`],
      windowsVerbatimArguments: false,
    })
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('Windows source settle bypasses the generated cmd command line', (): void => {
  const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-windows-settle-live-'))
  const root = path.join(workspace, 'source with spaces')

  try {
    const bin = path.join(root, '.hermes', 'bin')
    const witness = path.join(workspace, 'settled.txt')
    fs.mkdirSync(bin, { recursive: true })
    const pythonProbe = spawnSync('python', ['-c', 'import sys; print(sys.executable)'], { encoding: 'utf8' })
    expect(pythonProbe.status, pythonProbe.stderr || String(pythonProbe.error)).toBe(0)
    const python = pythonProbe.stdout.trim()
    fs.writeFileSync(path.join(bin, 'hermes.cmd'), `@"${python}" -I -c "import base64; exec(base64.b64decode('eA=='))" %*\r\n`)
    const prepareLaunch = path.join(root, 'hermes_cli', 'venv_sync.py')
    fs.mkdirSync(path.dirname(prepareLaunch), { recursive: true })
    fs.writeFileSync(prepareLaunch, `from pathlib import Path\ndef prepare_launch(root, args):\n    Path(${JSON.stringify(witness)}).write_text(str(root) + '\\n' + '\\n'.join(args))\n`)
    fs.writeFileSync(path.join(bin, 'hermes.exe'), 'locked historical launcher')
    const invocation = sourceRuntimeSettleCommand(root, process.env, 'win32')

    const result = spawnSync(invocation.command, invocation.args, {
      cwd: root, env: process.env, encoding: 'utf8', windowsHide: true,
      windowsVerbatimArguments: invocation.windowsVerbatimArguments,
    })

    expect(result.status, result.stderr || String(result.error)).toBe(0)
    expect(fs.readFileSync(witness, 'utf8').split(/\r?\n/)).toEqual([
      root,
      'status',
    ])
  } finally { fs.rmSync(workspace, { recursive: true, force: true }) }
})

test('update-window process checks use the isolated launch environment, not the driver environment', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-update-window-process-'))

  try {
    const executable = path.join(root, 'Hermes')
    const isolated = path.join(root, 'isolated-user-data')
    const driver = path.join(root, 'driver-user-data')
    fs.writeFileSync(executable, '')
    fs.mkdirSync(isolated)
    fs.mkdirSync(driver)
    const prior = process.env.HERMES_DESKTOP_USER_DATA_DIR
    process.env.HERMES_DESKTOP_USER_DATA_DIR = driver

    try {
      expect(() => assertUpdateWindowProcess(
        { executable, resources: root, userData: isolated },
        { executable, root, origin: 'source', userData: isolated },
      )).not.toThrow()
      expect(() => assertUpdateWindowProcess(
        { executable, resources: root, userData: isolated },
        { executable, root, origin: 'source', userData: driver },
      )).toThrow('OLD update window did not honor isolated userData')
    } finally {
      if (prior === undefined) {
        delete process.env.HERMES_DESKTOP_USER_DATA_DIR
      } else {
        process.env.HERMES_DESKTOP_USER_DATA_DIR = prior
      }
    }
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('predictSmokeHermesHome replays the bundle banner through the shared resolver', (): void => {
  const launchEnv = { HERMES_HOME: '/pinned/home', HERMES_DESKTOP_USER_DATA_DIR: '/pinned/userdata', LOCALAPPDATA: 'C:/Users/runner/AppData/Local' }
  // No baked env: the driver's own HERMES_HOME pin wins.
  expect(predictSmokeHermesHome(launchEnv, {}, 'linux', '/real/home')).toBe('/pinned/home')
  // HERMES_HOME cleared -> the <userData>/hermes-home fallback.
  expect(predictSmokeHermesHome(launchEnv, { HERMES_HOME: null }, 'linux', '/real/home')).toBe('/pinned/userdata/hermes-home')
  // Both cleared + baked suffix -> the platform default with that suffix.
  expect(predictSmokeHermesHome(launchEnv, { HERMES_HOME: null, HERMES_DESKTOP_USER_DATA_DIR: null, HERMES_DATA_DIR_SUFFIX: '-magic' }, 'linux', '/real/home')).toBe('/real/home/.hermes-magic')
  // On Windows the default derives from the sandboxed LOCALAPPDATA, not the OS home.
  expect(predictSmokeHermesHome(launchEnv, { HERMES_HOME: null, HERMES_DESKTOP_USER_DATA_DIR: null, HERMES_DATA_DIR_SUFFIX: '-magic' }, 'win32', 'C:/Users/real')).toBe('C:\\Users\\runner\\AppData\\Local\\hermes-magic')
})

test('readBundledBundleEnv reads the stamped defaults/clears and is absent when unstamped', (): void => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-bundlenv-'))

  try {
    expect(readBundledBundleEnv(path.join(root, 'agent-payload'))).toBeUndefined()
    fs.writeFileSync(path.join(root, 'install-stamp.json'), JSON.stringify({ payload: 'bundled', commit: 'a'.repeat(40), bundleEnv: { HERMES_HOME: null, SUFFIX: 'x' } }))
    expect(readBundledBundleEnv(path.join(root, 'agent-payload'))).toEqual({ HERMES_HOME: null, SUFFIX: 'x' })
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test.each<[Record<string, string | null> | undefined]>([
  [undefined], [{ HERMES_PREVIEW_FULL_CONNECTORS: '1' }], [{ HERMES_HOME: null }],
])(
  'bundled smoke admits a fresh home and seeds the resolved provider with defaults %j', async (bundleEnv: Record<string, string | null> | undefined): Promise<void> => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-bundle-clear-'))
  const home = path.join(root, 'home')
  const userData = path.join(root, 'root', 'user-data')
  // A real-but-dummy executable so admission passes and seeding runs; the
  // launcher itself is substituted below, so Playwright never spawns it (a
  // process that exits at once leaves Playwright with dangling rejections).
  const exe = path.join(root, 'root', 'fake-desktop')
  fs.mkdirSync(path.join(root, 'root'), { recursive: true })
  fs.writeFileSync(exe, '#!/bin/sh\nexit 1\n')
  fs.chmodSync(exe, 0o755)

  if (bundleEnv) {
    fs.writeFileSync(path.join(root, 'install-stamp.json'), JSON.stringify({ payload: 'bundled', bundleEnv }))
  }

  const refuseLaunch = async (): Promise<never> => { throw new Error('launch refused by test') }

  try {
    const mock = await startMockServer()

    try {
      // The bundled app's banner turns HERMES_HOME=null into HERMES_HOME='', so
      // resolveDesktopHermesHome falls to <userData>/hermes-home. The driver must
      // have seeded THAT home, not only the --home the caller named.
      await expect(runInstalledDesktopSmoke({ exe, root: path.join(root, 'root'), origin: 'bundled', home,
        'user-data': userData, out: root, phase: 'installed', 'expect-commit': 'a'.repeat(40) }, refuseLaunch)).rejects.toThrow('launch refused by test')

      for (const candidate of candidateSmokeHermesHomes(home, userData)) {
        expect(yaml.load(fs.readFileSync(path.join(candidate, 'config.yaml'), 'utf8'))).toMatchObject({ model: { provider: 'custom' } })
        const env = fs.readFileSync(path.join(candidate, '.env'), 'utf8')
        expect(env).toMatch(/MOCK_API_KEY=/)
        expect(env).toMatch(/OPENAI_API_KEY=/)
        expect(env).toMatch(/OPENAI_BASE_URL=http:\/\/127\.0\.0\.1:\d+\/v1/)
      }

      // Electron resolves shell folders before 'ready'; the sandboxed AppData/XDG
      // dirs must exist or Windows applyDesktopIdentity crashes at launch.
      for (const dir of ['AppData/Roaming', 'AppData/Local', '.config', '.local/share', '.cache']) {
        expect(fs.statSync(path.join(home, '.desktop-smoke-home', ...dir.split('/'))).isDirectory()).toBe(true)
      }

      if (bundleEnv) {
        const predicted = predictSmokeHermesHome(smokeEnvironment({}, home, userData), bundleEnv)
        const config = path.join(predicted, 'config.yaml')
        const before = fs.readFileSync(config)
        await expect(runInstalledDesktopSmoke({ exe, root: path.join(root, 'root'), origin: 'bundled', home,
          'user-data': userData, out: root, phase: 'installed', 'expect-commit': 'a'.repeat(40) }, refuseLaunch)).rejects.toThrow('refusing to seed an existing profile')
        expect(fs.readFileSync(config)).toEqual(before)
      }
    } finally {
      await mock.close()
    }
  } finally { fs.rmSync(root, { recursive: true, force: true }) }
})

test('writeEnvFile without a URL keeps the journey provider endpoint', (): void => {
  // The install e2e configures the mock provider through the CLI, which writes
  // OPENAI_BASE_URL/OPENAI_API_KEY. The desktop smoke then calls writeEnvFile with
  // no URL, and stripping the pair there deleted the endpoint the snapshot was
  // meant to record -- the upgrade's own .env sync later put it back, and the leg
  // failed as "the upgrade changed the user's own state".
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-env-'))

  try {
    writeEnvFile(home, 'e2e-mock-key', 'http://127.0.0.1:9000')
    expect(fs.readFileSync(path.join(home, '.env'), 'utf8')).toContain('OPENAI_BASE_URL=http://127.0.0.1:9000/v1')

    writeEnvFile(home)

    const after = fs.readFileSync(path.join(home, '.env'), 'utf8')
    expect(after).toContain('OPENAI_BASE_URL=http://127.0.0.1:9000/v1')
    expect(after).toContain('OPENAI_API_KEY=e2e-mock-key')
    expect(after).toContain('MOCK_API_KEY=e2e-mock-key')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})

test('re-writing the same provider config is byte-identical', (): void => {
  // Filtering the keys out and re-appending them moved a journey's own .env
  // entries on every call: same keys, same values, different bytes -- which the
  // user-state verifier reported as "0 deleted, 1 modified ... no key differs".
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'smoke-env-order-'))

  try {
    // The key is deliberately NOT last, with a comment after it: filtering the
    // managed keys out and re-appending them moved them past the journey's own
    // entries, which is the "no key differs, comments/order/blanks only" the
    // user-state verifier reported.
    const before = 'OPENAI_BASE_URL=http://127.0.0.1:9000/v1\n# keep me\nOTHER_TEST_VALUE=kept\n'
    fs.writeFileSync(path.join(home, '.env'), before)

    writeEnvFile(home, 'e2e-mock-key', 'http://127.0.0.1:9000')

    expect(fs.readFileSync(path.join(home, '.env'), 'utf8')).toBe(
      'OPENAI_BASE_URL=http://127.0.0.1:9000/v1\n# keep me\nOTHER_TEST_VALUE=kept\n'
      + 'MOCK_API_KEY=e2e-mock-key\nOPENAI_API_KEY=e2e-mock-key\n')
  } finally { fs.rmSync(home, { recursive: true, force: true }) }
})
