diff --git a/scripts/session-query-spill-command.spec.ts b/scripts/session-query-spill-command.spec.ts index d77b4093d2..74a64799c9 100644 --- a/scripts/session-query-spill-command.spec.ts +++ b/scripts/session-query-spill-command.spec.ts @@ -40,24 +40,24 @@ it.skipIf(process.platform === 'win32')('resolves the exact spill verifier, reta await adapter const command = 'file="' + saved.locator + '"' + "; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'" const spec = ctx.shell.resolve({ command }) - const output = await ctx.shell.run(spec) + const output = await ctx.shell.execute(spec).result() expect(output.timedOut).toBe(false) expect(output.exitCode).toBe(0) expect(output.stdout.text).toBe('SPILL_CANONICAL_OK\n') expect(spec.command).toBe(command) - const other = await ctx.shell.run(ctx.shell.resolve({ command: 'printf ordinary; exit 7' })) + const other = await ctx.shell.execute(ctx.shell.resolve({ command: 'printf ordinary; exit 7' })).result() expect(other.exitCode).toBe(7) expect(other.stdout.text).toBe('ordinary') - const unmatched = await ctx.shell.run(ctx.shell.resolve({ command: command + '; exit 9' })) + const unmatched = await ctx.shell.execute(ctx.shell.resolve({ command: command + '; exit 9' })).result() expect(unmatched.exitCode).toBe(9) expect(unmatched.stderr.text).toContain('No such file') const physical = ctx.fs.processPath(await ctx.fs.resolve(saved.locator)) await rm(physical) - const missing = await ctx.shell.run(spec) + const missing = await ctx.shell.execute(spec).result() expect(missing.exitCode).not.toBe(0) expect(missing.stdout.text).toBe('') await adapter.dispose() - const restored = await ctx.shell.run(spec) + const restored = await ctx.shell.execute(spec).result() expect(restored.exitCode).not.toBe(0) expect(restored.stderr.text).toContain(saved.locator) } finally { diff --git a/scripts/snapshot-spill-locators.spec.ts b/scripts/snapshot-spill-locators.spec.ts index 6bd0f2ef81..8b28e371aa 100644 --- a/scripts/snapshot-spill-locators.spec.ts +++ b/scripts/snapshot-spill-locators.spec.ts @@ -44,15 +44,19 @@ it.each([false, true])('keeps concurrent physical spill files private while reta expect(target.displayPath).toBe(ref.locator) const physicalPath = ctx.fs.processPath(target) expect(physicalPath.startsWith(await realpath(root))).toBe(true) + expect(await realpath(ctx.fs.processPathFromHostPath(ref.locator)!)).toBe(physicalPath) expect(await readFile(physicalPath, 'utf8')).toBe(content) expect(await ctx.fs.readText(target)).toBe(content) await expect(ctx.fs.resolve(join(locatorRoot, 'missing.txt'))).rejects.toThrow('not saved by this run') + expect(() => ctx.fs.processPathFromHostPath(join(locatorRoot, 'missing.txt'))).toThrow('not saved by this run') const ordinary = join(root, 'ordinary.txt') await writeFile(ordinary, 'ordinary') + expect(ctx.fs.processPathFromHostPath(ordinary)).toBe(ordinary) expect(await ctx.fs.readText(await ctx.fs.resolve(ordinary))).toBe('ordinary') await fork.dispose() const restored = await ctx.fs.resolve(ref.locator) expect(ctx.fs.processPath(restored)).not.toBe(physicalPath) + expect(ctx.fs.processPathFromHostPath(ref.locator)).toBe(ref.locator) expect(await ctx.fs.stat(restored)).toBeUndefined() return { root, physicalPath, locator: ref.locator, content } })) diff --git a/scripts/snapshot-spill-locators.ts b/scripts/snapshot-spill-locators.ts index 7c0762c289..c159db6e2b 100644 --- a/scripts/snapshot-spill-locators.ts +++ b/scripts/snapshot-spill-locators.ts @@ -1,4 +1,4 @@ -/** Fixture-only logical locators over real local spill files; preview budgets retain recorded path lengths. */ +/** Fixture-only logical locators for filesystem and subprocess reads of real spill files; preview budgets retain recorded path lengths. */ import type { Context } from '@deepseek-ai/cordis' import { join, relative, resolve, sep } from 'node:path' import type { SpillLocator } from '@deepseek-ai/dsh-spill' @@ -28,6 +28,17 @@ export function apply(ctx: Context, config: Config): void { const saveText = store.saveText // oxlint-disable-next-line typescript/unbound-method -- preserve method identity for restoration; calls bind the receiver. const resolvePath = fs.resolve + // oxlint-disable-next-line typescript/unbound-method -- preserve method identity for restoration; calls bind the receiver. + const processPathFromHostPath = fs.processPathFromHostPath + + const mappedPath = (path: string): string => { + const live = paths.get(path) + if (live !== undefined) return live + if (path.startsWith(locatorRoot + sep)) { + throw new Error('snapshot spill locator was not saved by this run') + } + return path + } ctx.effect(() => { store.saveText = async (input) => { const saved = await saveText.call(store, input) @@ -40,19 +51,15 @@ export function apply(ctx: Context, config: Config): void { return { ...saved, locator } } fs.resolve = async (path, opts) => { - const live = paths.get(path) - if (live !== undefined) { - const target = await resolvePath.call(fs, live, opts) - return { ...target, displayPath: path } - } - if (path.startsWith(locatorRoot + sep)) { - throw new Error('snapshot spill locator was not saved by this run') - } - return resolvePath.call(fs, path, opts) + const live = mappedPath(path) + const target = await resolvePath.call(fs, live, opts) + return live === path ? target : { ...target, displayPath: path } } + fs.processPathFromHostPath = path => processPathFromHostPath.call(fs, mappedPath(path)) return () => { store.saveText = saveText fs.resolve = resolvePath + fs.processPathFromHostPath = processPathFromHostPath paths.clear() } }) diff --git a/snapshots/session/session-query-spill/resolve-spill-command.mjs b/snapshots/session/session-query-spill/resolve-spill-command.mjs index f8ba29b29e..3cc6a097e8 100644 --- a/snapshots/session/session-query-spill/resolve-spill-command.mjs +++ b/snapshots/session/session-query-spill/resolve-spill-command.mjs @@ -6,16 +6,17 @@ export const inject = ['shell', 'fs'] export function apply(ctx) { const shell = ctx.shell const fs = ctx.fs - const run = shell.run + const execute = shell.execute const suffix = "; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'" ctx.effect(() => { - shell.run = async (spec) => { + shell.execute = (spec) => { const match = /^file="([^"]+)"/.exec(spec.command) - if (match === null || spec.command !== match[0] + suffix) return run.call(shell, spec) - const target = await fs.resolve(match[1]) - const path = fs.processPath(target).replaceAll("'", "'\"'\"'") - return run.call(shell, { ...spec, command: "file='" + path + "'" + suffix }) + if (match === null || spec.command !== match[0] + suffix) return execute.call(shell, spec) + const physical = fs.processPathFromHostPath(match[1]) + if (physical === undefined) throw new Error('snapshot spill path is not readable by the shell') + const path = physical.replaceAll("'", "'\"'\"'") + return execute.call(shell, { ...spec, command: "file='" + path + "'" + suffix }) } - return () => { shell.run = run } + return () => { shell.execute = execute } }) }