mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-22 12:58:09 +00:00
361 lines
15 KiB
TypeScript
361 lines
15 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
findGatewaySessionCreateLifecycleViolations,
|
|
findSessionAccessorBoundaryViolations,
|
|
findSessionCompactManualTrimBoundaryViolations,
|
|
findSessionAccessorWriteBoundaryViolations,
|
|
findSessionLifecycleCleanupBoundaryViolations,
|
|
findTranscriptWriterBoundaryViolations,
|
|
migratedBundledPluginSessionAccessorFiles,
|
|
migratedSessionLifecycleCleanupFiles,
|
|
migratedSessionCompactManualTrimFiles,
|
|
migratedSessionAccessorFiles,
|
|
migratedSessionAccessorWriteFiles,
|
|
migratedTranscriptWriterFiles,
|
|
} from "../../scripts/check-session-accessor-boundary.mjs";
|
|
|
|
describe("session accessor boundary guard", () => {
|
|
it("ratchets only the files migrated by the session accessor slices", () => {
|
|
expect(migratedSessionAccessorFiles).toEqual(
|
|
new Set([
|
|
"src/agents/embedded-agent-runner/compaction-successor-transcript.ts",
|
|
"src/agents/embedded-agent-runner/run/attempt.ts",
|
|
"src/agents/embedded-agent-runner/tool-result-truncation.ts",
|
|
"src/agents/embedded-agent-runner/transcript-rewrite.ts",
|
|
"src/agents/embedded-agent-runner/transcript-runtime-state.ts",
|
|
"src/auto-reply/reply/agent-runner-helpers.ts",
|
|
"src/auto-reply/reply/agent-runner.ts",
|
|
"src/auto-reply/reply/commands-subagents/action-info.ts",
|
|
"src/auto-reply/reply/followup-runner.ts",
|
|
"src/auto-reply/reply/queue/drain.ts",
|
|
"src/commands/export-trajectory.ts",
|
|
"src/commands/health.ts",
|
|
"src/commands/sandbox-explain.ts",
|
|
"src/commands/sessions-tail.ts",
|
|
"src/commands/sessions.ts",
|
|
"src/commands/status.agent-local.ts",
|
|
"src/commands/status.summary.ts",
|
|
"src/config/sessions/combined-store-gateway.ts",
|
|
"src/cron/isolated-agent/delivery-target.ts",
|
|
"src/cron/service/timer.ts",
|
|
"src/gateway/session-compaction-checkpoints.ts",
|
|
"src/gateway/session-utils.ts",
|
|
"src/gateway/sessions-resolve.ts",
|
|
"src/gateway/server-methods/sessions.ts",
|
|
"src/infra/outbound/message-action-tts.ts",
|
|
"src/tui/embedded-backend.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("ratchets only the bundled plugin files migrated by this slice", () => {
|
|
expect(migratedBundledPluginSessionAccessorFiles).toEqual(
|
|
new Set([
|
|
"extensions/discord/src/monitor/native-command-model-picker-apply.ts",
|
|
"extensions/discord/src/monitor/thread-session-close.ts",
|
|
"extensions/telegram/src/bot-handlers.runtime.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("ratchets only files migrated to session accessor writes", () => {
|
|
expect(migratedSessionAccessorWriteFiles).toEqual(
|
|
new Set([
|
|
"src/agents/command/attempt-execution.shared.ts",
|
|
"src/agents/command/session-store.ts",
|
|
"src/agents/embedded-agent-runner/run.ts",
|
|
"src/agents/embedded-agent-runner/run/attempt.ts",
|
|
"src/auto-reply/reply/abort-cutoff.runtime.ts",
|
|
"src/auto-reply/reply/agent-runner-cli-dispatch.ts",
|
|
"src/auto-reply/reply/agent-runner-execution.ts",
|
|
"src/auto-reply/reply/agent-runner-memory.ts",
|
|
"src/auto-reply/reply/agent-runner.ts",
|
|
"src/auto-reply/reply/body.ts",
|
|
"src/auto-reply/reply/commands-acp/lifecycle.ts",
|
|
"src/auto-reply/reply/commands-reset.ts",
|
|
"src/auto-reply/reply/directive-handling.impl.ts",
|
|
"src/auto-reply/reply/directive-handling.persist.ts",
|
|
"src/auto-reply/reply/dispatch-from-config.runtime.ts",
|
|
"src/auto-reply/reply/followup-runner.ts",
|
|
"src/auto-reply/reply/get-reply.ts",
|
|
"src/auto-reply/reply/model-selection.ts",
|
|
"src/auto-reply/reply/session-reset-model.ts",
|
|
"src/auto-reply/reply/session-updates.ts",
|
|
"src/auto-reply/reply/session-usage.ts",
|
|
"src/tui/embedded-backend.ts",
|
|
"src/config/sessions/cleanup-service.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("ratchets only the files migrated by the transcript writer slice", () => {
|
|
expect(migratedTranscriptWriterFiles).toEqual(
|
|
new Set([
|
|
"src/agents/command/attempt-execution.ts",
|
|
"src/agents/embedded-agent-runner/context-engine-maintenance.ts",
|
|
"src/config/sessions/transcript.ts",
|
|
"src/gateway/server-methods/chat.ts",
|
|
"src/gateway/server-methods/chat-transcript-inject.ts",
|
|
"src/sessions/user-turn-transcript.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("ratchets only compact manual trim gateway files", () => {
|
|
expect(migratedSessionCompactManualTrimFiles).toEqual(
|
|
new Set(["src/gateway/server-methods/sessions.ts"]),
|
|
);
|
|
});
|
|
|
|
it("ratchets only the lifecycle cleanup files migrated to backend cleanup", () => {
|
|
expect(migratedSessionLifecycleCleanupFiles).toEqual(
|
|
new Set([
|
|
"src/config/sessions/cleanup-service.ts",
|
|
"src/cron/session-reaper.ts",
|
|
"src/infra/heartbeat-runner.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("flags legacy reader imports", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
import { loadSessionStore, readSessionEntries as readEntries } from "../config/sessions.js";
|
|
import { readSessionEntry, readSessionStoreReadOnly } from "../config/sessions/store-load.js";
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'imports legacy session store access "loadSessionStore"' },
|
|
{ line: 2, reason: 'imports legacy session store access "readSessionEntries"' },
|
|
{ line: 3, reason: 'imports legacy session store access "readSessionEntry"' },
|
|
{ line: 3, reason: 'imports legacy session store access "readSessionStoreReadOnly"' },
|
|
]);
|
|
});
|
|
|
|
it("flags direct and namespace legacy access calls", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
loadSessionStore(storePath);
|
|
sessions.readSessionEntries(storePath);
|
|
sessions["loadSessionStore"](storePath);
|
|
readSessionStoreReadOnly(storePath);
|
|
resolveSessionStoreEntry({ store, sessionKey });
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'calls legacy session store access "loadSessionStore"' },
|
|
{ line: 3, reason: 'references legacy session store access "readSessionEntries"' },
|
|
{ line: 4, reason: 'references legacy session store access "loadSessionStore"' },
|
|
{ line: 5, reason: 'calls legacy session store access "readSessionStoreReadOnly"' },
|
|
{ line: 6, reason: 'calls legacy session store access "resolveSessionStoreEntry"' },
|
|
]);
|
|
});
|
|
|
|
it("flags aliased namespace reader references", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
const load = sessions.loadSessionStore;
|
|
const { readSessionEntries: readEntries } = sessions;
|
|
const { loadSessionStore } = sessions;
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'references legacy session store access "loadSessionStore"' },
|
|
{ line: 3, reason: 'aliases legacy session store access "readSessionEntries"' },
|
|
{ line: 4, reason: 'aliases legacy session store access "loadSessionStore"' },
|
|
]);
|
|
});
|
|
|
|
it("flags legacy whole-store writes", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
import { saveSessionStore, updateSessionStore } from "../config/sessions.js";
|
|
saveSessionStore(storePath, store);
|
|
updateSessionStore(storePath, update);
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'imports legacy session store access "saveSessionStore"' },
|
|
{ line: 2, reason: 'imports legacy session store access "updateSessionStore"' },
|
|
{ line: 3, reason: 'calls legacy session store access "saveSessionStore"' },
|
|
{ line: 4, reason: 'calls legacy session store access "updateSessionStore"' },
|
|
]);
|
|
});
|
|
|
|
it("allows migrated accessor reads", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
import { listSessionEntries } from "../config/sessions/session-accessor.js";
|
|
listSessionEntries({ storePath });
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("flags legacy writer imports and calls", () => {
|
|
expect(
|
|
findSessionAccessorWriteBoundaryViolations(`
|
|
import { applySessionStoreEntryPatch, saveSessionStore, updateSessionStore, updateSessionStoreEntry as updateEntry } from "../config/sessions.js";
|
|
saveSessionStore(storePath, store);
|
|
updateSessionStore(storePath, () => undefined);
|
|
sessions.updateSessionStoreEntry({ storePath, sessionKey, update });
|
|
applySessionStoreEntryPatch({ storePath, sessionKey, patch });
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'imports legacy session store writer "applySessionStoreEntryPatch"' },
|
|
{ line: 2, reason: 'imports legacy session store writer "saveSessionStore"' },
|
|
{ line: 2, reason: 'imports legacy session store writer "updateSessionStore"' },
|
|
{ line: 2, reason: 'imports legacy session store writer "updateSessionStoreEntry"' },
|
|
{ line: 3, reason: 'calls legacy session store writer "saveSessionStore"' },
|
|
{ line: 4, reason: 'calls legacy session store writer "updateSessionStore"' },
|
|
{ line: 5, reason: 'references legacy session store writer "updateSessionStoreEntry"' },
|
|
{ line: 6, reason: 'calls legacy session store writer "applySessionStoreEntryPatch"' },
|
|
]);
|
|
});
|
|
|
|
it("allows migrated accessor writes", () => {
|
|
expect(
|
|
findSessionAccessorWriteBoundaryViolations(`
|
|
import { updateSessionEntry } from "../config/sessions/session-accessor.js";
|
|
updateSessionEntry({ storePath, sessionKey }, () => undefined);
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("flags legacy transcript writer imports", () => {
|
|
expect(
|
|
findTranscriptWriterBoundaryViolations(`
|
|
import { appendSessionTranscriptMessage } from "../config/sessions/transcript-append.js";
|
|
import { emitSessionTranscriptUpdate as emitUpdate } from "../sessions/transcript-events.js";
|
|
import { rewriteTranscriptEntriesInSessionFile } from "../agents/embedded-agent-runner/transcript-rewrite.js";
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'imports legacy transcript writer "appendSessionTranscriptMessage"' },
|
|
{ line: 3, reason: 'imports legacy transcript writer "emitSessionTranscriptUpdate"' },
|
|
{
|
|
line: 4,
|
|
reason: 'imports legacy transcript writer "rewriteTranscriptEntriesInSessionFile"',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("flags direct and namespace legacy transcript writer calls", () => {
|
|
expect(
|
|
findTranscriptWriterBoundaryViolations(`
|
|
appendSessionTranscriptMessage({ transcriptPath, message });
|
|
transcriptEvents.emitSessionTranscriptUpdate({ sessionFile });
|
|
transcriptAppend["appendSessionTranscriptMessage"]({ transcriptPath, message });
|
|
transcriptRewrite.rewriteTranscriptEntriesInSessionFile({ sessionFile, request });
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'calls legacy transcript writer "appendSessionTranscriptMessage"' },
|
|
{ line: 3, reason: 'references legacy transcript writer "emitSessionTranscriptUpdate"' },
|
|
{ line: 4, reason: 'references legacy transcript writer "appendSessionTranscriptMessage"' },
|
|
{
|
|
line: 5,
|
|
reason: 'references legacy transcript writer "rewriteTranscriptEntriesInSessionFile"',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("allows migrated transcript writer helpers", () => {
|
|
expect(
|
|
findTranscriptWriterBoundaryViolations(`
|
|
import { appendTranscriptMessage, publishTranscriptUpdate } from "../config/sessions/session-accessor.js";
|
|
appendTranscriptMessage(scope, { message });
|
|
publishTranscriptUpdate(scope, { messageId });
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("flags legacy writers inside the gateway sessions.create lifecycle", () => {
|
|
expect(
|
|
findGatewaySessionCreateLifecycleViolations(`
|
|
const handlers = {
|
|
"sessions.create": async () => {
|
|
await updateSessionStore(storePath, () => undefined);
|
|
ensureSessionTranscriptFile(params);
|
|
},
|
|
"sessions.patch": async () => {
|
|
await updateSessionStore(storePath, () => undefined);
|
|
},
|
|
};
|
|
`),
|
|
).toEqual([
|
|
{ line: 4, reason: 'calls legacy sessions.create lifecycle writer "updateSessionStore"' },
|
|
{
|
|
line: 5,
|
|
reason: 'calls legacy sessions.create lifecycle writer "ensureSessionTranscriptFile"',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("allows the gateway sessions.create lifecycle accessor seam", () => {
|
|
expect(
|
|
findGatewaySessionCreateLifecycleViolations(`
|
|
const handlers = {
|
|
"sessions.create": async () => {
|
|
await createSessionEntryWithTranscript(scope, createEntry);
|
|
},
|
|
};
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("flags gateway manual compact trim file mutations", () => {
|
|
expect(
|
|
findSessionCompactManualTrimBoundaryViolations(`
|
|
import { archiveFileOnDisk } from "../session-utils.js";
|
|
import { readRecentSessionTranscriptLines } from "../session-transcript-readers.js";
|
|
const tail = readRecentSessionTranscriptLines(scope);
|
|
const archived = archiveFileOnDisk(filePath, "bak");
|
|
`),
|
|
).toEqual([
|
|
{ line: 2, reason: 'imports legacy session store manual compact trim "archiveFileOnDisk"' },
|
|
{
|
|
line: 3,
|
|
reason:
|
|
'imports legacy session store manual compact trim "readRecentSessionTranscriptLines"',
|
|
},
|
|
{
|
|
line: 4,
|
|
reason: 'calls legacy session store manual compact trim "readRecentSessionTranscriptLines"',
|
|
},
|
|
{ line: 5, reason: 'calls legacy session store manual compact trim "archiveFileOnDisk"' },
|
|
]);
|
|
});
|
|
|
|
it("flags direct lifecycle cleanup helper usage", () => {
|
|
expect(
|
|
findSessionLifecycleCleanupBoundaryViolations(`
|
|
import { archiveRemovedSessionTranscripts } from "../config/sessions/store.js";
|
|
import { cleanupArchivedSessionTranscripts } from "../gateway/session-utils.fs.js";
|
|
archiveRemovedSessionTranscripts({ removedSessionFiles, referencedSessionIds, storePath, reason: "deleted" });
|
|
cleanupArchivedSessionTranscripts({ directories, rules });
|
|
`),
|
|
).toEqual([
|
|
{
|
|
line: 2,
|
|
reason: 'imports legacy session store lifecycle cleanup "archiveRemovedSessionTranscripts"',
|
|
},
|
|
{
|
|
line: 3,
|
|
reason:
|
|
'imports legacy session store lifecycle cleanup "cleanupArchivedSessionTranscripts"',
|
|
},
|
|
{
|
|
line: 4,
|
|
reason: 'calls legacy session store lifecycle cleanup "archiveRemovedSessionTranscripts"',
|
|
},
|
|
{
|
|
line: 5,
|
|
reason: 'calls legacy session store lifecycle cleanup "cleanupArchivedSessionTranscripts"',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("ignores comments and strings that describe legacy readers", () => {
|
|
expect(
|
|
findSessionAccessorBoundaryViolations(`
|
|
// loadSessionStore and readSessionEntries used to be called here.
|
|
const description = "loadSessionStore";
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
});
|