Files
2026-09-13 23:25:51 -07:00

326 lines
11 KiB
JavaScript

const { app } = require('electron')
const assert = require('node:assert/strict')
const fs = require('node:fs/promises')
const { existsSync, appendFileSync } = require('node:fs')
const { join } = require('node:path')
const { DatabaseSync } = require('node:sqlite')
const output = process.argv[2]
const phase = process.argv[3]
const profile = join(output, 'profile')
app.setPath('userData', profile)
app.setPath('sessionData', join(profile, 'chromium'))
app.disableHardwareAcceleration()
globalThis.__fileIdChildren = []
const report = {
phase,
pid: process.pid,
execPath: process.execPath,
versions: process.versions,
stages: [],
children: [],
stderr: [],
ipc: []
}
globalThis.__fileIdObserveChild = (child) => {
globalThis.__fileIdChildren.push(child)
child.stderr.on('data', (chunk) => report.stderr.push(String(chunk)))
child.on('message', (message) =>
report.ipc.push({ pid: child.pid, type: message.type, operation: message.operation })
)
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const sessionId = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
const markers = {
initial: 'quartzinitialmarker',
append: 'cobaltappendedmarker',
replacement: 'velvetreplacedmarker',
disabled: 'tungstendisabledmarker'
}
let api
let registration
let transport
const endpoint = `\\\\.\\pipe\\orca-file-id-${process.pid}`
let settings = {}
let transcript
const database = join(profile, 'ai-vault', 'session-search.sqlite')
function record(stage, data = {}) {
const entry = { stage, at: Date.now(), ...data }
report.stages.push(entry)
appendFileSync(join(output, `${phase}-stages.jsonl`), `${JSON.stringify(entry)}\n`)
console.log(stage, JSON.stringify(data))
}
function inspect(marker) {
const db = new DatabaseSync(database, { readOnly: true })
try {
const query = db.prepare(
'SELECT CAST(ino AS TEXT) AS ino, byte_offset, size_bytes, mtime_ms FROM files WHERE path = ?'
)
const rows = db
.prepare(`SELECT s.session_id FROM messages_fts f JOIN messages m ON m.id=f.rowid
JOIN sessions s ON s.id=m.session_row_id WHERE messages_fts MATCH ?`)
.all(marker)
return { file: query.get(transcript), sessions: rows.map((row) => row.session_id) }
} finally {
db.close()
}
}
async function query(marker, present) {
const result = await api.runProcess({
program: process.env.ORCA_FILE_ID_NODE,
args: [
join(output, 'client.cjs'),
endpoint,
JSON.stringify({ query: `"${marker}"`, scope: 'conversation', freshness: 'indexed' })
],
env: process.env,
timeoutMs: 15_000
})
assert.equal(result.code, 0, result.stderr)
const external = JSON.parse(result.stdout)
assert.equal(external.response.ok, true, JSON.stringify(external))
const response = external.response.result
assert.equal(response.kind, 'results', JSON.stringify(response))
assert.equal(
response.hits.some((hit) => hit.sessionId === sessionId),
present,
`RPC ${marker}`
)
const disk = inspect(marker)
assert.equal(disk.sessions.includes(sessionId), present, `DB ${marker}`)
const actual = await fs.stat(transcript)
assert.equal(
disk.file.byte_offset,
actual.size,
'Committed cursor covers actual transcript bytes'
)
assert.equal(disk.file.size_bytes, actual.size)
assert.equal(disk.file.mtime_ms, actual.mtimeMs)
record('query', { marker, present, clientPid: external.pid, endpoint, response, disk })
}
async function status() {
return api.rpc('aiVault.searchStatus', {})
}
async function waitFor(predicate, label, timeout = 30_000) {
const until = Date.now() + timeout
while (Date.now() < until) {
const current = await status()
if (predicate(current)) {
return current
}
await sleep(200)
}
throw new Error(`Timed out: ${label}`)
}
async function policy(next) {
const before = settings
settings = next
await fs.writeFile(join(profile, 'fixture-settings.json'), JSON.stringify(settings))
api.applySessionSearchSettingsChange(before, settings)
}
async function stopChild() {
const children = [...globalThis.__fileIdChildren]
api.resetAiVaultScannerServiceForTests()
for (const child of children) {
const until = Date.now() + 5000
while (child.exitCode === null && child.signalCode === null && Date.now() < until) {
await sleep(50)
}
assert.ok(
child.exitCode !== null || child.signalCode !== null,
`Child ${child.pid} did not exit`
)
if (!report.children.some((row) => row.pid === child.pid)) {
report.children.push({
pid: child.pid,
spawnfile: child.spawnfile,
spawnargs: child.spawnargs,
exitCode: child.exitCode,
signalCode: child.signalCode
})
}
}
}
async function run() {
assert.equal(process.env.ORCA_BACKGROUND_LAUNCH, '1')
await fs.mkdir(profile, { recursive: true })
api = require('./production.js')
api.setAppEnvironment({
getPath: (name) => (name === 'userData' ? profile : app.getPath(name)),
getAppPath: () => output,
getVersion: () => app.getVersion(),
isPackaged: () => false,
onWillQuit: (handler) => app.on('will-quit', handler),
exit: (code) => app.exit(code),
getAppMetrics: () => app.getAppMetrics()
})
const dispatcher = new api.RpcDispatcher({
methods: api.AI_VAULT_METHODS,
runtime: { getRuntimeId: () => `file-id-${process.pid}` }
})
transport = new api.UnixSocketTransport({ endpoint, kind: 'named-pipe' })
transport.onMessage((message, reply) => {
void dispatcher
.dispatch(JSON.parse(message))
.then((response) => reply(JSON.stringify(response)))
})
await transport.start()
record('rpc-transport', {
endpoint,
auth: 'fixture; production auth/metadata server not booted',
methods: 'production AI_VAULT_METHODS',
dispatcher: 'production RpcDispatcher'
})
const roots = {
...api.isolatedScanRoots(join(output, 'roots')),
wslHomeDirs: [],
additionalCodexSessionsDirs: [],
executionHostId: 'local'
}
process.env.ORCA_FILE_ID_ROOTS = JSON.stringify(roots)
await fs.mkdir(roots.claudeProjectsDir, { recursive: true })
transcript = join(roots.claudeProjectsDir, `${sessionId}.jsonl`)
if (phase === 'restart') {
settings = JSON.parse(await fs.readFile(join(profile, 'fixture-settings.json'), 'utf8'))
assert.equal(settings.aiVaultSearch.enabled, true)
const beforeRestart = inspect(markers.replacement)
registration = api.installChildSessionSearchService({
dataRoot: profile,
getSettings: () => settings
})
await waitFor((s) => s.filesIndexed === 1 && s.phase === 'current', 'Electron host restart')
await query(markers.append, false)
await query(markers.replacement, true)
assert.deepEqual(inspect(markers.replacement), beforeRestart)
record('app-host-restart-same-db')
await disableAndCheck()
return
}
const body = `${api.claudeLines([markers.initial], sessionId, 0).join('\n')}\n`
await fs.writeFile(transcript, body)
let raw = await fs.stat(transcript, { bigint: true })
for (let n = 0; raw.ino <= BigInt(Number.MAX_SAFE_INTEGER) && n < 512; n++) {
await fs.unlink(transcript)
await fs.writeFile(transcript, body)
raw = await fs.stat(transcript, { bigint: true })
}
assert.ok(raw.ino > BigInt(Number.MAX_SAFE_INTEGER), 'NTFS must supply an actual unsafe inode')
record('fixture', {
transcript,
rawIno: raw.ino.toString(),
numericIno: BigInt(Number(raw.ino)).toString()
})
registration = api.installChildSessionSearchService({
dataRoot: profile,
getSettings: () => settings
})
assert.ok(registration)
assert.equal((await status()).enabled, false)
assert.deepEqual(await api.rpc('aiVault.searchSessions', { query: markers.initial }), {
kind: 'unavailable',
reason: 'disabled'
})
assert.equal(existsSync(database), false)
record('default-off')
await policy({ aiVaultSearch: { enabled: true, historyDays: null } })
const initial = await waitFor(
(s) => s.filesIndexed === 1 && s.phase === 'current',
'initial indexing'
)
assert.equal(inspect(markers.initial).file.ino, BigInt(Number(raw.ino)).toString())
await query(markers.initial, true)
record('initial-indexing', { status: initial })
let recent = initial
for (let n = 0; n < 2; n++) {
const previous = recent
recent = await waitFor(
(s) => s.lastReconcileAt > previous.lastReconcileAt,
'recent timer reconciliation'
)
assert.equal(
recent.lastSweepCompletedAt,
initial.lastSweepCompletedAt,
'Expected a recent pass'
)
await query(markers.initial, true)
record('recent-pass', { status: recent })
}
for (let n = 0; n < 2; n++) {
await api.reconcileSessionSearchInService()
await query(markers.initial, true)
record('full-pass', { status: await status() })
}
await fs.appendFile(
transcript,
`${api.claudeLines([markers.append], sessionId, 10).join('\n')}\n`
)
const frozen = new Date(Math.floor((await fs.stat(transcript)).mtimeMs))
await fs.utimes(transcript, frozen, frozen)
await api.reconcileSessionSearchInService()
await query(markers.append, true)
const before = await fs.stat(transcript)
const replacement = `${transcript}.replacement`
const replacementBody = (await fs.readFile(transcript, 'utf8')).replaceAll(
markers.append,
markers.replacement
)
assert.equal(Buffer.byteLength(replacementBody), before.size)
await fs.writeFile(replacement, replacementBody)
await fs.rename(replacement, transcript)
await fs.utimes(transcript, before.atime, before.mtime)
const after = await fs.stat(transcript)
assert.equal(after.size, before.size)
assert.equal(after.mtimeMs, before.mtimeMs)
assert.notEqual(after.ino, before.ino)
await api.reconcileSessionSearchInService()
await query(markers.append, false)
await query(markers.replacement, true)
record('replacement', { beforeIno: before.ino, afterIno: after.ino })
await stopChild()
await waitFor((s) => s.filesIndexed === 1 && s.phase === 'current', 'scanner restart')
await query(markers.append, false)
await query(markers.replacement, true)
record('service-restart')
}
async function disableAndCheck() {
await policy({ aiVaultSearch: { enabled: false, historyDays: null } })
await waitFor((s) => !s.enabled, 'disable')
const snapshot = inspect(markers.replacement)
await fs.appendFile(
transcript,
`${api.claudeLines([markers.disabled], sessionId, 20).join('\n')}\n`
)
await api.reconcileSessionSearchInService()
await sleep(21_000)
assert.deepEqual(await api.rpc('aiVault.searchSessions', { query: markers.disabled }), {
kind: 'unavailable',
reason: 'disabled'
})
assert.deepEqual(inspect(markers.replacement), snapshot)
assert.deepEqual(inspect(markers.disabled).sessions, [])
record('disabled-no-indexing')
}
app.whenReady().then(async () => {
try {
await run()
report.ok = true
} catch (error) {
report.ok = false
report.error = error.stack
console.error(error)
} finally {
registration?.dispose()
await transport?.stop()
if (api) {
await stopChild().catch((error) => {
report.cleanupError = String(error)
report.ok = false
})
}
if (report.stderr.some((text) => text.includes('[ai-vault-search]'))) {
report.ok = false
}
await fs.writeFile(join(output, `report-${phase}.json`), JSON.stringify(report, null, 2))
app.exit(report.ok ? 0 : 1)
}
})