DSH-Arch-Setup/plugins/dsh-touched-git/test/git-write-runtime.test.js
Шурупов Илья Викторович cf9443753e Add one-command Arch DSH setup
Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation.
2026-09-12 19:13:36 +03:00

250 lines
12 KiB
JavaScript

import assert from 'node:assert/strict'
import { execFile } from 'node:child_process'
import { mkdtemp, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { homedir, tmpdir } from 'node:os'
import { join } from 'node:path'
import { promisify } from 'node:util'
import test from 'node:test'
import { TouchedGitStore } from '../lib/index.js'
import { registerGitWriteGuard } from '../lib/git-write-guard.js'
const installedRuntime = createRequire(process.env.DSH_RUNTIME_PACKAGE_JSON || join(homedir(), '.local/lib/node_modules/@deepseek-ai/dsh/package.json'))
const { Context } = await import(installedRuntime.resolve('@deepseek-ai/cordis'))
const { SystemPrompt } = await import(installedRuntime.resolve('@deepseek-ai/dsh-system-prompt'))
const { ToolRuntime, defineTool } = await import(installedRuntime.resolve('@deepseek-ai/dsh-tools'))
const { LocalFileSystem } = await import(installedRuntime.resolve('@deepseek-ai/dsh-fs-local'))
const { apply: applyObservationPolicy } = await import(installedRuntime.resolve('@deepseek-ai/dsh-fs-observation-policy'))
const execute = promisify(execFile)
const mutationOutput = {
type: 'object',
additionalProperties: false,
properties: {
path: { type: 'string', required: true },
before: { required: true, oneOf: [{ type: 'string' }, { type: 'null' }] },
after: { type: 'string', required: true },
},
}
function registerFixtureMutation(context, name, counters) {
const parameters = name === 'write'
? { content: { type: 'string', required: true } }
: { old_string: { type: 'string', required: true }, new_string: { type: 'string', required: true } }
context.tools.register(defineTool({
name,
description: `Integration fixture ${name} using the real filesystem provider.`,
parameters: { file_path: { type: 'string', required: true }, ...parameters },
output: {
schema: mutationOutput,
render: (_args, value) => [{ type: 'text', text: `${name} completed: ${value.path}` }],
},
async execute(args, execution) {
counters.bodies[name]++
const target = await context.fs.resolve(args.file_path, { cwd: execution.agent.session.header.cwd, signal: execution.signal })
const intent = await context.waterfall(`fs/${name}-intent`, target, execution, () => undefined)
const outcome = name === 'write'
? await context.fs.writeText(target, args.content, intent, execution.signal)
: await context.fs.editText(target, { oldString: args.old_string, newString: args.new_string, replaceAll: false }, intent, execution.signal)
counters.mutations[name]++
context.emit('fs/observed', target, { kind: 'present', version: outcome.version }, execution)
return { path: target.displayPath, before: outcome.before, after: outcome.after }
},
}))
}
function registerFixtureRead(context) {
context.tools.register(defineTool({
name: 'read',
description: 'Integration fixture read that records the provider observation.',
parameters: { file_path: { type: 'string', required: true } },
output: {
schema: {
type: 'object', additionalProperties: false,
properties: { text: { type: 'string', required: true } },
},
render: (_args, value) => [{ type: 'text', text: value.text }],
},
async execute(args, execution) {
const target = await context.fs.resolve(args.file_path, { cwd: execution.agent.session.header.cwd, signal: execution.signal })
const text = await context.fs.readText(target, execution.signal)
const info = await context.fs.stat(target, execution.signal)
context.emit('fs/observed', target, { kind: 'present', version: info.version }, execution)
return { text }
},
}))
}
async function fixture(t, options = {}) {
const root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-git-runtime-')))
const context = new Context()
let disposeGuard
t.after(async () => {
disposeGuard?.()
await context.fiber.dispose()
await rm(root, { recursive: true, force: true })
})
const repository = join(root, 'repository')
await mkdir(repository)
await execute('git', ['init', '--quiet', repository])
const store = new TouchedGitStore(join(root, 'store.json'))
await store.ready
new SystemPrompt(context, { includeHarnessIdentity: false })
new ToolRuntime(context)
new LocalFileSystem(context, { cwd: root, diffBasisMaxBytes: 1024 * 1024 })
applyObservationPolicy(context)
const counters = { bodies: { write: 0, edit: 0 }, mutations: { write: 0, edit: 0 } }
registerFixtureMutation(context, 'write', counters)
registerFixtureMutation(context, 'edit', counters)
registerFixtureRead(context)
const recorded = []
disposeGuard = registerGitWriteGuard(context, {
store,
onSuccessfulMutation: options.onSuccessfulMutation ?? (async mutation => { recorded.push(mutation) }),
})
const agent = { session: { header: { id: 'integration-session', cwd: repository } } }
let callNumber = 0
return {
root, repository, context, store, counters, recorded, agent,
async register() {
await store.upsert(agent.session.header.id, { id: repository, path: repository, workingDirectory: repository, repository, ok: true })
},
run(name, args, extra = {}) {
return context.tools.execute({ name, arguments: args, callId: `integration-call-${++callNumber}`, agent, signal: new AbortController().signal, ...extra })
},
}
}
function mutationArgs(name, filePath) {
return name === 'write'
? { file_path: filePath, content: 'updated content' }
: { file_path: filePath, old_string: 'original', new_string: 'updated' }
}
function resultText(result) {
return result.content.filter(block => block.type === 'text').map(block => block.text).join('\n')
}
for (const name of ['write', 'edit']) {
test(`actual registry denies unregistered ${name} before entering its body or changing the file`, async t => {
const world = await fixture(t)
const target = join(world.repository, 'existing.txt')
await writeFile(target, 'original content')
const result = await world.run(name, mutationArgs(name, target))
assert.equal(result.isError, true)
assert.match(result.error.message, /not registered/)
assert.equal(Object.hasOwn(result, 'value'), false)
assert.equal(world.counters.bodies[name], 0)
assert.equal(world.counters.mutations[name], 0)
assert.equal(await readFile(target, 'utf8'), 'original content')
assert.equal(world.recorded.length, 0)
assert.ok(Object.isFrozen(result))
})
test(`actual registry accepts registered observed ${name} and publishes a canonical successful result`, async t => {
const world = await fixture(t)
await world.register()
const target = join(world.repository, 'existing.txt')
await writeFile(target, 'original content')
const observed = await world.run('read', { file_path: target })
assert.equal(observed.isError, false)
const result = await world.run(name, mutationArgs(name, target))
assert.equal(result.isError, false)
assert.equal(await readFile(target, 'utf8'), 'updated content')
assert.equal(world.counters.bodies[name], 1)
assert.equal(world.counters.mutations[name], 1)
assert.deepEqual(result.value, { path: target, before: 'original content', after: 'updated content' })
assert.ok(Object.isFrozen(result.value))
assert.equal(world.recorded.length, 1)
assert.equal(world.recorded[0].repository, world.repository)
assert.equal(world.recorded[0].toolName, name)
assert.equal(world.recorded[0].sessionId, world.agent.session.header.id)
})
test(`actual registry preserves outside-Git ${name} success and appends a warning without altering output schema`, async t => {
const world = await fixture(t)
const target = join(world.root, 'outside.txt')
await writeFile(target, 'original content')
await world.run('read', { file_path: target })
const result = await world.run(name, mutationArgs(name, target))
assert.equal(result.isError, false)
assert.equal(await readFile(target, 'utf8'), 'updated content')
assert.equal(world.counters.mutations[name], 1)
assert.match(resultText(result), /WARNING:.*outside a Git repository/)
assert.deepEqual(Object.keys(result.value).sort(), ['after', 'before', 'path'])
assert.equal(world.recorded[0].repository, null)
assert.match(world.recorded[0].warning, /outside a Git repository/)
})
test(`actual monotonic guard blocks ${name} when an outer pre listener skips classification`, async t => {
const world = await fixture(t)
await world.register()
const target = join(world.repository, 'existing.txt')
await writeFile(target, 'original content')
await world.run('read', { file_path: target })
world.context.on('tools/pre-execute', async (execution, next) => execution.name === name ? { kind: 'allow' } : next(), true)
const result = await world.run(name, mutationArgs(name, target))
assert.equal(result.isError, true)
assert.match(result.error.message, /pre-execution check did not complete/)
assert.equal(world.counters.bodies[name], 0)
assert.equal(world.counters.mutations[name], 0)
assert.equal(await readFile(target, 'utf8'), 'original content')
})
test(`real observation policy still rejects unread ${name} through the prepended Git intent hook`, async t => {
const world = await fixture(t)
await world.register()
const target = join(world.repository, 'existing.txt')
await writeFile(target, 'original content')
const unread = await world.run(name, mutationArgs(name, target))
assert.equal(unread.isError, true)
assert.equal(unread.error.info.code, 'FS_NOT_OBSERVED')
assert.equal(world.counters.bodies[name], 1)
assert.equal(world.counters.mutations[name], 0)
assert.equal(world.recorded.length, 0)
assert.equal(await readFile(target, 'utf8'), 'original content')
await world.run('read', { file_path: target })
const observed = await world.run(name, mutationArgs(name, target))
assert.equal(observed.isError, false)
assert.equal(world.counters.mutations[name], 1)
assert.equal(await readFile(target, 'utf8'), 'updated content')
})
}
test('actual registry creates a new registered file without requiring a prior read', async t => {
const world = await fixture(t)
await world.register()
const target = join(world.repository, 'new-directory', 'new.txt')
const result = await world.run('write', mutationArgs('write', target))
assert.equal(result.isError, false)
assert.equal(result.value.before, null)
assert.equal(await readFile(target, 'utf8'), 'updated content')
})
test('actual registry materializes warning contexts when downstream post policy replaces the canonical value', async t => {
const world = await fixture(t)
const target = join(world.root, 'outside.txt')
world.context.on('tools/post-execute', async (execution, result, next) => execution.name === 'write' && !result.isError
? { kind: 'accept', value: { ...result.value, after: 'policy-projected value' } }
: next())
const result = await world.run('write', mutationArgs('write', target))
assert.equal(result.isError, false)
assert.equal(result.value.after, 'policy-projected value')
assert.equal(await readFile(target, 'utf8'), 'updated content')
assert.equal(result.additionalContexts.length, 1)
assert.equal(result.additionalContexts[0].role, 'user')
assert.equal(result.additionalContexts[0].source.plugin, 'dsh-touched-git')
assert.match(result.additionalContexts[0].content[0].text, /outside a Git repository/)
assert.ok(Object.isFrozen(result.additionalContexts[0]))
})
test('actual successful mutation is not falsely rejected when persistence callback fails', async t => {
const world = await fixture(t, { onSuccessfulMutation: async () => { throw new Error('fixture persistence failure') } })
await world.register()
const target = join(world.repository, 'new.txt')
const result = await world.run('write', mutationArgs('write', target))
assert.equal(result.isError, false)
assert.equal(await readFile(target, 'utf8'), 'updated content')
assert.match(resultText(result), /operation completed.*could not be saved: fixture persistence failure/)
})