Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,8 +1298,7 @@
"src/node/services/tools/todo.ts": ["@/node/utils/writeFileAtomic", "fs/promises"],
"src/node/services/tools/xum_agents_path.ts": ["fs/promises"],
"src/node/services/tools/xum_agents_read.ts": ["fs/promises"],
"src/node/services/tools/xum_agents_write.ts": ["fs/promises"],
"src/node/services/compactionCancellation.ts": ["fs"]
"src/node/services/tools/xum_agents_write.ts": ["fs/promises"]
}
}
]
Expand Down
2 changes: 0 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,6 @@ const HOST_IMPORT_ALLOW = {
"src/node/services/tools/xum_agents_path.ts": ["fs/promises"],
"src/node/services/tools/xum_agents_read.ts": ["fs/promises"],
"src/node/services/tools/xum_agents_write.ts": ["fs/promises"],
// Phase 2: each move PR removes its own pairs.
"src/node/services/compactionCancellation.ts": ["fs"],
};

const SHARED_RESTRICTED_PATHS = [AI_TOKENIZER_ENCODING_RESTRICTION];
Expand Down
2 changes: 0 additions & 2 deletions scripts/noHostImports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,6 @@ const FROZEN_ALLOW_PAIRS = [
"src/node/services/tools/xum_agents_path.ts fs/promises",
"src/node/services/tools/xum_agents_read.ts fs/promises",
"src/node/services/tools/xum_agents_write.ts fs/promises",
// Phase 2 (session files): the 15 pairs of the history layer.
"src/node/services/compactionCancellation.ts fs",
];

function allowMap(): Record<string, string[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1930,8 +1930,13 @@ describe("compaction cancellation runtime", () => {
spyOn(fresh.historyService, "cleanupCompactionFollowUp").mockImplementationOnce(
async (...args) => {
const result = await cleanup(...args);
const foreignHistory = new HistoryService(h.config);
const foreign = new CompactionCancellation(
new FileCompactionCancellationStorage(new HistoryService(h.config), workspaceId)
new FileCompactionCancellationStorage(
foreignHistory,
workspaceId,
foreignHistory.files
)
);
await foreign.cancel();
successorNonce = (await foreign.read())?.nonce;
Expand Down
24 changes: 11 additions & 13 deletions src/node/services/agentSession.turnCompletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ describe("AgentSession turn completion", () => {
},
})
);
const open = fileIO.open;
const open = fs.open;
let failed = false;
const reading = spyOn(fileIO, "open").mockImplementation(
const reading = spyOn(fs, "open").mockImplementation(
async (...args: Parameters<typeof open>) => {
if (args[0] === storage.path && !failed) {
failed = true;
Expand Down Expand Up @@ -139,23 +139,21 @@ describe("AgentSession turn completion", () => {
const stream = spyOn(h.aiService, "streamMessage");
const storage = h.historyService.getCompactionCancellationStorage(workspaceId);
const failure = Object.assign(new Error(`${code}: cancellation read unavailable`), { code });
const open = fileIO.open;
let reading: ReturnType<typeof spyOn<typeof fileIO, "open">> | undefined;
const open = fs.open;
let reading: ReturnType<typeof spyOn<typeof fs, "open">> | undefined;
const warning = spyOn(log, "warn");
try {
expect(await h.session.sendMessage("original", sendOptions)).toEqual(Ok(undefined));
expect(await h.session.cancelCompaction(true)).toEqual(Ok(undefined));
const cancellationBytes = await fileIO.readFile(storage.path);
reading = spyOn(fileIO, "open").mockImplementation(
async (...args: Parameters<typeof open>) => {
if (args[0] === storage.path) {
entered.resolve();
await release.promise;
throw failure;
}
return open(...args);
reading = spyOn(fs, "open").mockImplementation(async (...args: Parameters<typeof open>) => {
if (args[0] === storage.path) {
entered.resolve();
await release.promise;
throw failure;
}
);
return open(...args);
});
const streamError = {
messageId: "assistant-1",
error: "provider failed",
Expand Down
152 changes: 82 additions & 70 deletions src/node/services/compactionCancellation.storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ describe("inactive real cancellation storage", () => {
mutationCommitted.mockClear();
h = await createTestHistoryService();
foreign = new HistoryService(h.config);
storage = new FileCompactionCancellationStorage(h.historyService, workspaceId);
storage = new FileCompactionCancellationStorage(
h.historyService,
workspaceId,
h.historyService.files
);
state = new CompactionCancellation(storage);
sessionDir = path.dirname(storage.path);
expect(
Expand Down Expand Up @@ -509,14 +513,18 @@ describe("inactive real cancellation storage", () => {
expect(initial.version).toBe(1);
if (kind === "scoped") {
const other = new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
);
await other.read();
await other.narrow(initial.nonce, summary);
}
settled.resolve(kind !== "failed");
expect(await stopping).toBe("applied");
const persisted = await new FileCompactionCancellationStorage(foreign, workspaceId).read();
const persisted = await new FileCompactionCancellationStorage(
foreign,
workspaceId,
foreign.files
).read();
expect(persisted?.nonce).toBe(initial.nonce);
expect(persisted?.version).toBe(kind === "joined" ? 2 : 1);
if (kind === "joined") {
Expand All @@ -525,7 +533,7 @@ describe("inactive real cancellation storage", () => {
);
expect(
await new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
).read()
).toEqual(persisted);
} else expect(persisted?.settledGeneration).toBeUndefined();
Expand Down Expand Up @@ -656,7 +664,7 @@ describe("inactive real cancellation storage", () => {
// These real writes acquire the same locks: waiting for physical settlement must release them.
if (successorKind === "nonce") {
await new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
).cancel({ retainUntilReplacement: true });
} else {
await foreign.withCompactionStorageLock(workspaceId, async (_dir, checkLock) => {
Expand Down Expand Up @@ -755,7 +763,7 @@ describe("inactive real cancellation storage", () => {
expect(await storage.read()).toBeNull();
failure.mockRestore();
const successor = new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
);
expect(await successor.cancel({ retainUntilReplacement: true })).toBe("applied");
const successorBytes = await fs.readFile(storage.path);
Expand Down Expand Up @@ -914,20 +922,18 @@ describe("inactive real cancellation storage", () => {
nodeFs.writeFileSync(generationPath, "foreign-generation");
};
if (phase === "retirement") {
const open = nodeFs.promises.open;
spyOn(nodeFs.promises, "open").mockImplementation(
async (...args: Parameters<typeof open>) => {
const handle = await open(...args);
if (args[0] === storage.path) {
const close = handle.close.bind(handle);
spyOn(handle, "close").mockImplementation(async () => {
await close();
if (!displaced) displace();
});
}
return handle;
const open = fs.open;
spyOn(fs, "open").mockImplementation(async (...args: Parameters<typeof open>) => {
const handle = await open(...args);
if (args[0] === storage.path) {
const close = handle.close.bind(handle);
spyOn(handle, "close").mockImplementation(async () => {
await close();
if (!displaced) displace();
});
}
);
return handle;
});
} else {
const target = phase === "generation" ? generationPath : storage.path;
afterCompactionStaging(target, () => {
Expand Down Expand Up @@ -1134,40 +1140,38 @@ describe("inactive real cancellation storage", () => {
let totalRead = 0;
let largestBuffer = 0;
let closed = false;
const open = nodeFs.promises.open;
spyOn(nodeFs.promises, "open").mockImplementation(
async (...args: Parameters<typeof open>) => {
const handle = await open(...args);
if (args[0] === storage.path) {
const close = handle.close.bind(handle);
spyOn(handle, "close").mockImplementation(async () => {
await close();
closed = true;
});
const read = handle.read.bind(handle);
spyOn(handle, "read").mockImplementation((async (
buffer: Buffer,
offset: number,
length: number,
position: number
) => {
reads++;
largestBuffer = Math.max(largestBuffer, buffer.byteLength);
if (kind === "read error") throw new Error("descriptor read failed");
// Exercise legal short reads; a single read must not mistake them for EOF.
const result = await read(
buffer,
offset,
Math.min(length, SESSION_HISTORY_MAX_LINE_BYTES / 16),
position
);
totalRead += result.bytesRead;
return result;
}) as typeof handle.read);
}
return handle;
const open = fs.open;
spyOn(fs, "open").mockImplementation(async (...args: Parameters<typeof open>) => {
const handle = await open(...args);
if (args[0] === storage.path) {
const close = handle.close.bind(handle);
spyOn(handle, "close").mockImplementation(async () => {
await close();
closed = true;
});
const read = handle.read.bind(handle);
spyOn(handle, "read").mockImplementation((async (
buffer: Buffer,
offset: number,
length: number,
position: number
) => {
reads++;
largestBuffer = Math.max(largestBuffer, buffer.byteLength);
if (kind === "read error") throw new Error("descriptor read failed");
// Exercise legal short reads; a single read must not mistake them for EOF.
const result = await read(
buffer,
offset,
Math.min(length, SESSION_HISTORY_MAX_LINE_BYTES / 16),
position
);
totalRead += result.bytesRead;
return result;
}) as typeof handle.read);
}
);
return handle;
});
if (kind === "limit") expect(await storage.read()).toEqual(record("bounded"));
else
await assert.rejects(
Expand All @@ -1190,7 +1194,7 @@ describe("inactive real cancellation storage", () => {
expect(await storage.read()).toBeNull();
await state.cancel();
const first = (await state.read())!;
const other = new FileCompactionCancellationStorage(foreign, workspaceId);
const other = new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files);
expect(await other.read()).toEqual(first);
const loaded = (await other.read())!;
loaded.nonce = "changed locally";
Expand Down Expand Up @@ -1268,7 +1272,7 @@ describe("inactive real cancellation storage", () => {
await state.cancel();
const old = (await state.read())!;
const successor = new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
);
await successor.cancel({ retainUntilReplacement: true });
const expected = await successor.read();
Expand Down Expand Up @@ -1410,26 +1414,34 @@ describe("inactive real cancellation storage", () => {
storage.mutate(mutation, () => true, mutationCommitted),
/not configured/
);
const refusing = new FileCompactionCancellationStorage(foreign, workspaceId, () =>
Promise.resolve(() => Promise.resolve(false))
const refusing = new FileCompactionCancellationStorage(
foreign,
workspaceId,
foreign.files,
() => Promise.resolve(() => Promise.resolve(false))
);
await assert.rejects(
refusing.mutate(mutation, () => true, mutationCommitted),
/not verified/
);
expect(await storage.read()).toEqual(retained);
let current = true;
const verifying = new FileCompactionCancellationStorage(foreign, workspaceId, (witness) => {
expect(witness.nonce).toBe(retained.nonce);
expect(nodeFs.existsSync(historyWriteLockPath(h.config.rootDir, workspaceId))).toBe(false);
return Promise.resolve(async () => {
expect(
await fs.readFile(historyWriteLockPath(h.config.rootDir, workspaceId), "utf8")
).not.toBe("");
current = false;
return true;
});
});
const verifying = new FileCompactionCancellationStorage(
foreign,
workspaceId,
foreign.files,
(witness) => {
expect(witness.nonce).toBe(retained.nonce);
expect(nodeFs.existsSync(historyWriteLockPath(h.config.rootDir, workspaceId))).toBe(false);
return Promise.resolve(async () => {
expect(
await fs.readFile(historyWriteLockPath(h.config.rootDir, workspaceId), "utf8")
).not.toBe("");
current = false;
return true;
});
}
);
expect(await verifying.mutate(mutation, () => current, mutationCommitted)).toBe("superseded");
expect(await storage.read()).toEqual(retained);
// This injected authority exercises the seam only; real accepted-row proof belongs to H2b.
Expand Down Expand Up @@ -1545,7 +1557,7 @@ describe("inactive real cancellation storage", () => {
expect(state.blocksRecovery).toBe(true);
expect(await storage.read()).toEqual(old);
const successor = new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
);
await successor.cancel();
expect(await state.retry()).toBe("superseded");
Expand Down Expand Up @@ -1601,7 +1613,7 @@ describe("inactive real cancellation storage", () => {
failingRename.mockRestore();
if (change === "Stop") {
await new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
).cancel();
} else {
await foreign.getContinuousCompactionJournal(workspaceId).advanceGeneration();
Expand Down Expand Up @@ -1876,7 +1888,7 @@ describe("inactive real cancellation storage", () => {
await fs.writeFile(storage.path, "{bad");
await assert.rejects(storage.read(), MalformedCompactionCancellationError);
const successor = new CompactionCancellation(
new FileCompactionCancellationStorage(foreign, workspaceId)
new FileCompactionCancellationStorage(foreign, workspaceId, foreign.files)
);
await successor.cancel({ retainUntilReplacement: true });
const committed = mock(() => undefined);
Expand Down
Loading
Loading