* feat(native): add UI and dispatch investigation workflows * feat(ghidra): support macOS host validation * docs: update native investigation capabilities * feat(artifacts): decode compiled Interface Builder archives * feat(ghidra): expose bounded p-code value flow * test(ghidra): split host and cross-format verification * fix(verify): follow complete result contracts * refactor: remove unused authorization and schema helpers * refactor: remove unused evidence result alias * fix(ghidra): report jump-table evidence precisely * fix(verify): align browser scenario fixture schema * fix(setup): compare parsed client config structurally * test(ghidra): verify AArch64 jump-table mappings * fix(ghidra): recover AArch64 byte jump tables * test(artifacts): verify compiled interface builder archives * feat(native): trace UI actions through handlers * fix(tests): update native trace and shutdown fixtures * fix(verification): diagnose browser scenario failures * fix(browser): wrap scenario snapshots in capture state * fix(native): bind and bound investigation workflows * fix(artifacts): preserve bounded interface builder evidence * fix(ghidra): enforce admitted hosts and preflight tools * fix(native): preserve trace cancellation and bounds * test(application): expect bound analysis target evidence * fix(native): preserve investigation bounds and cancellation * fix(application): preserve bundle and unresolved trace evidence * style(test): format artifact subject fixture
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
import { isDeepStrictEqual } from "node:util";
|
|
import { z } from "zod";
|
|
|
|
import type { SetupClient } from "./SupportedClients.js";
|
|
|
|
/** Validated client document and its server table, preserving unrelated settings. */
|
|
export interface ClientConfigurationDocument {
|
|
readonly document: Record<string, unknown>;
|
|
readonly servers: Record<string, unknown>;
|
|
readonly serversKey: "mcp_servers" | "mcpServers";
|
|
}
|
|
|
|
const objectSchema = z.record(z.string(), z.unknown());
|
|
|
|
/** Compare parsed configuration values without depending on parser prototypes. */
|
|
export const clientConfigurationValuesEqual = (
|
|
left: unknown,
|
|
right: unknown,
|
|
): boolean =>
|
|
isDeepStrictEqual(
|
|
normalizeConfigurationValue(left),
|
|
normalizeConfigurationValue(right),
|
|
);
|
|
|
|
const normalizeConfigurationValue = (value: unknown): unknown => {
|
|
if (Array.isArray(value)) return value.map(normalizeConfigurationValue);
|
|
if (typeof value !== "object" || value === null || value instanceof Date)
|
|
return value;
|
|
return Object.fromEntries(
|
|
Object.entries(value)
|
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
.map(([key, nested]) => [key, normalizeConfigurationValue(nested)]),
|
|
);
|
|
};
|
|
|
|
/** Parse the client format and reject malformed root or server-table values. */
|
|
export const parseClientConfiguration = (
|
|
text: string,
|
|
format: SetupClient["format"],
|
|
): ClientConfigurationDocument => {
|
|
const document = objectSchema.parse(
|
|
format === "toml" ? parseToml(text) : JSON.parse(text),
|
|
);
|
|
const serversKey = format === "toml" ? "mcp_servers" : "mcpServers";
|
|
const value = document[serversKey];
|
|
const servers = value === undefined ? {} : objectSchema.parse(value);
|
|
return { document, servers, serversKey };
|
|
};
|
|
|
|
/** Serialize a validated client document using the selected format. */
|
|
export const serializeClientConfiguration = (
|
|
document: Record<string, unknown>,
|
|
format: SetupClient["format"],
|
|
): string =>
|
|
format === "toml"
|
|
? stringifyToml(document)
|
|
: `${JSON.stringify(document, null, 2)}\n`;
|