/** * Integration test: the plugin against the REAL DSH composition, not fakes. * * Boots a Cordis context with the real `dsh-storage` hub, the real * `dsh-storage-domain` facility, and the real `dsh-workspace` registry, then * mounts this plugin and drives the same session-create flow the API gateway * drives. Only two peers are stubbed: session persistence (header-only listing, * as the registry's own suite does) and the session controller's ensure/attach * body. * * The plugin lives outside the DSH checkout, so the real packages are imported * by absolute path from the built `lib/` trees. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { mkdtemp, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' const CHECKOUT = '/Users/staff/myProject/deepseek-harness/packages' const CORDIS = '/Users/staff/myProject/deepseek-harness/vendor/cordis/lib/index.js' const { Context } = await import(CORDIS) const Storage = (await import(`${CHECKOUT}/storage/storage/lib/index.js`)).default const { DomainFacility } = await import(`${CHECKOUT}/storage/storage-domain/lib/index.js`) const WorkspaceRegistry = (await import(`${CHECKOUT}/workspace/workspace/lib/index.js`)).default const pluginModule = await import('../index.js') /** The logical session format version the real header schema stamps. */ const SESSION_FORMAT_VERSION = 3 /** * A minimal in-memory KV backend: one object per unit, exactly the shape the * domain layer expects. The registry's own suite uses an equivalent helper. */ function memoryBackend() { const units = new Map() return { kv: { async open(descriptor) { const state = units.get(descriptor.name) ?? { tables: {}, global: null } units.set(descriptor.name, state) let closed = false const assertOpen = () => { if (closed) throw new Error('closed') } return { async loadAll() { assertOpen() return structuredClone(state) }, async putRecord(table, key, value) { assertOpen() ;(state.tables[table] ??= {})[key] = structuredClone(value) }, async deleteRecord(table, key) { assertOpen() delete state.tables[table]?.[key] }, async setGlobal(value) { assertOpen() state.global = structuredClone(value) }, async close() { closed = true }, } }, }, async close() {}, } } /** A header-only session persistence peer plus a live-header list for attach checks. */ function headerPersistence(headers) { return { async list() { return [...headers].map(header => ({ header, revision: `rev-${header.id}` })) }, open() { throw new Error('event bodies must not be opened') }, stat() { throw new Error('per-session stat must not be needed') }, } } /** A session/log stand-in: the registry only reads `session.header`. */ function createSession(id, cwd) { return { id, header: { version: SESSION_FORMAT_VERSION, id, createdAt: Date.now(), isSeeded: false, cwd, }, events: [], } } /** Settle queued microtasks and timers so plugin startup effects have run. */ async function settle() { for (let index = 0; index < 8; index += 1) await new Promise(resolve => setTimeout(resolve, 0)) } /** * Compose the real stack, mount the plugin, and drive session creation the way * the API gateway does: resolve the Workspace path, create the session with it * as cwd, then attach. */ async function boot(options = {}) { const ctx = new Context() await ctx.plugin(Storage) ctx.storage.backend.register('memory', options.backend ?? memoryBackend()) const facility = new DomainFacility(ctx, { backend: 'memory', routes: {} }) ctx.storage.mount('domain', facility) ctx.provide('storageDomain', facility) const headers = [] ctx.provide('sessionPersistence', headerPersistence(headers)) const live = new Map() ctx.provide('sessions', { get: id => live.get(id), list: () => [...live.values()], }) // The tool registry is a dependency of the plugin; a stub keeps this test // about isolation while still exercising injection ordering. const tools = [] ctx.provide('tools', { register(definition) { tools.push(definition) return () => {} }, }) /** * The session controller's create body: cwd in, session + attach out. It * resolves the registry through the context at call time, exactly like the * real `SessionCommandController`, so service ordering cannot hide a bug. */ const controller = { async create(request) { const workspace = request.workspaceId === undefined ? undefined : ctx.workspaceRegistry.get(request.workspaceId) const cwd = workspace?.path ?? request.cwd ?? '/default' const id = request.sessionId ?? `session-${Math.random().toString(16).slice(2, 10)}` const session = createSession(id, cwd) live.set(id, session) headers.push(session.header) // The real controller attaches AFTER the agent exists, so the registry's // header read must see the live session. if (workspace !== undefined) await workspace.attachSession(id) return { sessionId: id } }, } ctx.provide('sessionController', controller) await ctx.plugin(WorkspaceRegistry) // Mount exactly what the loader mounts: `unwrapExports` hands the Loader a // module's default export, which carries `inject` as plugin metadata. await ctx.plugin(pluginModule.default) await settle() return { ctx, controller, tools } } /** * One fresh temporary project directory per test. `realpath` is load-bearing: * the registry canonicalizes every Workspace path, and on macOS the temp root * itself is a symlink (`/var` -> `/private/var`). */ async function stage() { return await realpath(await mkdtemp(join(tmpdir(), 'dsh-isolation-int-'))) } test('a session in an isolating Workspace gets its own folder, still grouped there', async () => { const root = await stage() const { ctx } = await boot() try { const workspace = await ctx.workspaceRegistry.create(root) const result = await ctx.isolatedSessions.setIsolated(workspace.id, true) assert.equal(result.isolates, true) const created = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-11112222-3333-4444-5555-666677778888', }) const session = ctx.sessions.get(created.sessionId) assert.equal(session.header.cwd, join(root, 'session-11112222')) assert.equal((await stat(session.header.cwd)).isDirectory(), true) // Membership survives the registry's canonical-cwd filter: the stock // `sessionIds` getter must still account the session to the project. const view = ctx.workspaceRegistry.get(workspace.id) assert.deepEqual(view.sessionIds, [created.sessionId]) assert.deepEqual(ctx.workspaceRegistry.list().map(w => w.sessionIds), [[created.sessionId]]) } finally { await rm(root, { recursive: true, force: true }) } }) test('without the marker, sessions keep sharing the Workspace directory', async () => { const root = await stage() const { ctx } = await boot() try { const workspace = await ctx.workspaceRegistry.create(root) const created = await ctx.sessionController.create({ workspaceId: workspace.id }) assert.equal(ctx.sessions.get(created.sessionId).header.cwd, root) assert.deepEqual(ctx.workspaceRegistry.get(workspace.id).sessionIds, [created.sessionId]) } finally { await rm(root, { recursive: true, force: true }) } }) test('two concurrent sessions in one isolating project cannot collide', async () => { const root = await stage() const { ctx } = await boot() try { const workspace = await ctx.workspaceRegistry.create(root) await ctx.isolatedSessions.setIsolated(workspace.id, true) const first = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-aaaaaaaa-1111' }) const second = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-bbbbbbbb-2222' }) const one = ctx.sessions.get(first.sessionId).header.cwd const two = ctx.sessions.get(second.sessionId).header.cwd assert.notEqual(one, two) assert.equal(one.startsWith(root), true) assert.equal(two.startsWith(root), true) // Both write the same relative file: isolation is what keeps them apart. await writeFile(join(one, 'notes.txt'), 'first') await writeFile(join(two, 'notes.txt'), 'second') const back = await readFile(join(one, 'notes.txt'), 'utf8') assert.equal(back, 'first') assert.deepEqual( new Set(ctx.workspaceRegistry.get(workspace.id).sessionIds), new Set([first.sessionId, second.sessionId]), ) } finally { await rm(root, { recursive: true, force: true }) } }) test('the registry keeps exactly one owner and persists across a restart', async () => { const root = await stage() // One medium across both boots: that is what makes this a restart. const backend = memoryBackend() const { ctx } = await boot({ backend }) try { const workspace = await ctx.workspaceRegistry.create(root) await ctx.isolatedSessions.setIsolated(workspace.id, true) const created = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-cccccccc-3333' }) const sessionDir = ctx.sessions.get(created.sessionId).header.cwd // A second registry over the same medium proves bootstrap resumes the // isolated session into its project instead of orphaning it. const restarted = new Context() await restarted.plugin(Storage) restarted.storage.backend.register('memory', backend) const facility = new DomainFacility(restarted, { backend: 'memory', routes: {} }) restarted.storage.mount('domain', facility) restarted.provide('storageDomain', facility) restarted.provide('sessionPersistence', headerPersistence([ctx.sessions.get(created.sessionId).header])) restarted.provide('sessions', { get: () => undefined, list: () => [] }) restarted.provide('tools', { register: () => () => {} }) restarted.provide('sessionController', { create: async request => request }) await restarted.plugin(WorkspaceRegistry) await restarted.plugin(pluginModule.default) await settle() const registry = restarted.workspaceRegistry assert.equal(registry.list().length, 1) assert.deepEqual(registry.list()[0].sessionIds, [created.sessionId]) assert.deepEqual((await registry.resolveByPath(sessionDir)), undefined) assert.equal((await registry.resolveByPath(root)).id, workspace.id) } finally { await rm(root, { recursive: true, force: true }) } }) test('toggling isolation off stops new folders and keeps old sessions attached', async () => { const root = await stage() const { ctx } = await boot() try { const workspace = await ctx.workspaceRegistry.create(root) await ctx.isolatedSessions.setIsolated(workspace.id, true) const isolated = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-dddddddd-4444' }) assert.notEqual(ctx.sessions.get(isolated.sessionId).header.cwd, root) await ctx.isolatedSessions.setIsolated(workspace.id, false) const shared = await ctx.sessionController.create({ workspaceId: workspace.id, sessionId: 'session-eeeeeeee-5555' }) assert.equal(ctx.sessions.get(shared.sessionId).header.cwd, root) // The isolated session created while the marker existed stays in the project. assert.deepEqual( new Set(ctx.workspaceRegistry.get(workspace.id).sessionIds), new Set([isolated.sessionId, shared.sessionId]), ) } finally { await rm(root, { recursive: true, force: true }) } }) test('the registry patch is reversible', async () => { const root = await stage() const { ctx } = await boot() try { await ctx.workspaceRegistry.create(root) // A path toggle needs no Workspace registration: the decision is durable in // the directory itself, so the next scan picks the marker up. const on = await ctx.isolatedSessions.setIsolatedPath(root, true) assert.deepEqual(on, { path: root, isolates: true }) assert.deepEqual(ctx.isolatedSessions.list(), [root]) const off = await ctx.isolatedSessions.setIsolatedPath(root, false) assert.deepEqual(off, { path: root, isolates: false }) assert.deepEqual(ctx.isolatedSessions.list(), []) } finally { await rm(root, { recursive: true, force: true }) } })