fix(desktop): preserve significant keyfile value whitespace (#9077)

* fix(desktop): preserve significant keyfile value whitespace

* fix(desktop): preserve keyfile whitespace in exported verifier

* test(process): align symlink fixture with filesystem checkpoints

* test(frida): assert the CLI spawn Evidence contract

* test(mcp): use an unsupported analysis-view parent

* test(browser): separate source-location line bounds from parity
This commit is contained in:
ShuaixinHuang authored and GitHub committed 2026-11-06 14:27:41 +08:00
1 parent c7b0e0dec6
commit c92cbcaf28
4 files changed
+61 -6

No files matched your search

+4 -2
View File
@@ -205,8 +205,10 @@ def _keyfile_line(raw: str) -> tuple:
header = GROUP_HEADER.match(line)
if header:
return ("group", header.group(1))
key, equals, value = line.partition("=")
return ("entry", key.strip(), value.strip()) if equals and key else ("other",)
key, equals, value = raw.partition("=")
key = key.strip()
# Only ASCII space and tab immediately after '=' are padding.
return ("entry", key, value.lstrip(" \t")) if equals and key else ("other",)
def _each_line(text: str):
+28
View File
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import type { JsonValue } from "../jsonValue.js";
import { diffStateViews } from "./stateChanges.js";
import { keyfileView } from "./stateFormats.js";
import { resolvePointer } from "./viewPointer.js";
describe("diffStateViews", () => {
@@ -84,6 +85,33 @@ describe("diffStateViews", () => {
});
describe("diffStateViews fidelity", () => {
it("preserves significant keyfile whitespace in views and differences", () => {
const view = (suffix: string, unicode: string, padding: string) =>
keyfileView(
`[ui]\r\nsuffix=${suffix}\r\nunicode=${unicode}\r\npadded=${padding}\r\n`,
);
const before = view("hello ", "\u00a0hello\u00a0", " hello");
const after = view("hello ", "\u00a0hello\u00a0\u00a0", "\thello");
expect(before.ui).toEqual({
suffix: "hello ",
unicode: "\u00a0hello\u00a0",
padded: "hello",
});
expect(after.ui).toEqual({
suffix: "hello ",
unicode: "\u00a0hello\u00a0\u00a0",
padded: "hello",
});
expect(
diffStateViews("keyfile", { groups: before }, { groups: after }),
).toMatchObject({
kind: "keys",
changed: ["ui/suffix", "ui/unicode"],
changed_count: 2,
complete: true,
});
});
it("keeps empty containers as values", () => {
expect(diffStateViews("json", {}, { items: [] })).toMatchObject({
added: ["/items"],
+7 -4
View File
@@ -49,13 +49,16 @@ const keyfileLine = (raw: string): KeyfileLine => {
return { kind: "skip" };
const name = /^\[(?<name>[^\]]+)\]$/u.exec(line)?.groups?.name;
if (name !== undefined) return { kind: "group", name };
const equals = line.indexOf("=");
return equals <= 0
const equals = raw.indexOf("=");
if (equals < 0) return { kind: "other" };
const key = raw.slice(0, equals).trim();
return key.length === 0
? { kind: "other" }
: {
kind: "entry",
key: line.slice(0, equals).trim(),
value: line.slice(equals + 1).trim(),
key,
// Only ASCII space and tab immediately after '=' are padding.
value: raw.slice(equals + 1).replace(/^[ \t]+/u, ""),
};
};
@@ -10,6 +10,7 @@ import {
} from "../../../src/domain/desktop/appExportPython.js";
import {
detectStateFormat,
keyfileView,
isJsonText,
jsonView,
textualView,
@@ -397,3 +398,24 @@ it("spells infinite and negative-zero SQLite reals as REA's SQLite reader does",
expect(JSON.parse(cells)).toEqual(["Infinity", "-Infinity", "-0", 0, 1.5]);
});
it("keeps the same keyfile whitespace as the retained state view", async () => {
const inputs = [
"[ui]\nsuffix=hello \nunicode=\u00a0hello\u00a0\npadded= hello\n",
"[ui]\r\nsuffix=hello \r\nunicode=\u00a0hello\u00a0\u00a0\r\npadded=\thello\r\n",
];
const imports = PYTHON_IMPORTS.split("\n")
.filter((line) => !line.includes("agentenv_protocol"))
.join("\n");
const program = `${imports}${PYTHON_HELPERS}
print(json.dumps([_text_view("settings.ini", text, len(text.encode()))["view"]["groups"] for text in json.load(sys.stdin)]))
`;
const root = await createTestTempDirectory("rea-keyfile-python-");
const script = join(root, "fixture.py");
await writeFile(script, program, "utf8");
const output = execFileSync("python3", ["-I", "-B", "-X", "utf8", script], {
input: JSON.stringify(inputs),
encoding: "utf8",
});
expect(JSON.parse(output)).toEqual(inputs.map(keyfileView));
});