mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
fix(session): compare entrypoint file identity
Fixes #8844 Supersedes #8849 Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
90fd58d373
commit
eee8c5fa97
2 files changed
+67
-9
No files matched your search
+29
-8
@@ -1,3 +1,4 @@
|
||||
import { statSync } from "node:fs";
|
||||
import { dirname, basename, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -24,6 +25,24 @@ const packageRoot = (): string => {
|
||||
: resolve(moduleDirectory, "..");
|
||||
};
|
||||
|
||||
const compareServerPaths = (
|
||||
expected: string,
|
||||
observed: string,
|
||||
): "aligned" | "mismatch" | "unknown" => {
|
||||
if (expected === observed) return "aligned";
|
||||
if (process.platform === "win32")
|
||||
return resolve(expected) === observed ? "aligned" : "mismatch";
|
||||
try {
|
||||
const selected = statSync(expected, { bigint: true });
|
||||
const running = statSync(observed, { bigint: true });
|
||||
return selected.dev === running.dev && selected.ino === running.ino
|
||||
? "aligned"
|
||||
: "mismatch";
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
/** Build one runtime identity without claiming alignment from absent observations. */
|
||||
export const createServerIdentity = (input: {
|
||||
readonly startedAt: string;
|
||||
@@ -35,6 +54,10 @@ export const createServerIdentity = (input: {
|
||||
}) => {
|
||||
const catalog = input.catalog;
|
||||
const serverPath = input.commandPath || undefined;
|
||||
const pathComparison =
|
||||
input.expected?.server_path === undefined || serverPath === undefined
|
||||
? "unknown"
|
||||
: compareServerPaths(input.expected.server_path, serverPath);
|
||||
const reasons: string[] = [];
|
||||
if (
|
||||
input.expected?.package_version !== undefined &&
|
||||
@@ -46,17 +69,12 @@ export const createServerIdentity = (input: {
|
||||
input.expected.catalog_digest !== catalog.digests.combined_sha256
|
||||
)
|
||||
reasons.push("catalog_digest_mismatch");
|
||||
if (
|
||||
input.expected?.server_path !== undefined &&
|
||||
serverPath !== undefined &&
|
||||
resolve(input.expected.server_path) !== serverPath
|
||||
)
|
||||
reasons.push("registration_path_mismatch");
|
||||
if (pathComparison === "mismatch") reasons.push("registration_path_mismatch");
|
||||
const observedExpectation =
|
||||
input.expected !== undefined &&
|
||||
Object.values(input.expected).some((value) => value !== undefined);
|
||||
const pathComparable =
|
||||
input.expected?.server_path === undefined || serverPath !== undefined;
|
||||
input.expected?.server_path === undefined || pathComparison !== "unknown";
|
||||
return {
|
||||
package: {
|
||||
name: PRODUCT_IDENTITY.packageName,
|
||||
@@ -105,7 +123,10 @@ export const createServerIdentity = (input: {
|
||||
: input.expected?.server_path !== undefined &&
|
||||
serverPath === undefined
|
||||
? "The server entrypoint path is unavailable, so registration identity cannot be compared."
|
||||
: "Supply expected package, catalog, or registration identity to compare the live server.",
|
||||
: input.expected?.server_path !== undefined &&
|
||||
pathComparison === "unknown"
|
||||
? "The expected or running server entrypoint could not be inspected, so registration identity cannot be compared."
|
||||
: "Supply expected package, catalog, or registration identity to compare the live server.",
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { ok as resultOk } from "../../../src/domain/result.js";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
@@ -245,6 +245,43 @@ describe("server and catalog identity", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"compares the selected entrypoint through symlink and parent traversal",
|
||||
async () => {
|
||||
const root = await createTestTempDirectory("server-identity-path-");
|
||||
const scripts = join(root, "package", "scripts");
|
||||
const scratch = join(root, "scratch");
|
||||
await mkdir(scripts, { recursive: true });
|
||||
await mkdir(scratch);
|
||||
const canonical = join(scripts, "rea.mjs");
|
||||
const other = join(root, "other.mjs");
|
||||
await writeFile(canonical, "entrypoint");
|
||||
await writeFile(other, "other entrypoint");
|
||||
await symlink(scripts, join(scratch, "link"));
|
||||
const selected = `${scratch}/link/../scripts/rea.mjs`;
|
||||
const identity = (server_path: string) =>
|
||||
createServerIdentity({
|
||||
catalog: DEFAULT_CATALOG_IDENTITY,
|
||||
startedAt: "2026-07-13T00:00:00.000Z",
|
||||
commandPath: canonical,
|
||||
expected: { server_path },
|
||||
});
|
||||
|
||||
expect(identity(selected).alignment).toMatchObject({
|
||||
state: "aligned",
|
||||
reasons: [],
|
||||
});
|
||||
expect(identity(other).alignment).toMatchObject({
|
||||
state: "mcp_server_restart_required",
|
||||
reasons: ["registration_path_mismatch"],
|
||||
});
|
||||
expect(identity(join(root, "missing.mjs")).alignment).toMatchObject({
|
||||
state: "unknown",
|
||||
reasons: [],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("provider capability inventory", () => {
|
||||
it("reports composed, host, and target-specific availability truthfully", () => {
|
||||
const policy = {
|
||||
|
||||
Reference in new issue
Block a user