test: add deterministic runtime config chain race regression

This commit is contained in:
2026-08-11 12:16:25 +02:00
parent 1bca663ace
commit cf88e2df86
2 changed files with 103 additions and 10 deletions
@@ -560,3 +560,52 @@ test.each(["leaf", "ancestor"] as const)("actual harness rejects canonical %s sw
})).toThrow();
} finally { rmSync(f.root, { recursive: true, force: true }); }
});
test("actual harness rejects a workspace chain swap between config and manifest traversal", () => {
const f = fixture();
try {
const lease = f.factory.acquireSession(f.snapshotPath);
const python = join(process.cwd(), "..", "harness", ".venv", "bin", "python");
const helper = join(process.cwd(), "..", "harness", "tht");
const workspaceRoot = join(f.factoryInput.dataRoot, "sessions", workspace);
const code = `import os,sys
from pathlib import Path
sys.path.insert(0, sys.argv[3])
from tht.config import ConfigError, _read_runtime_config_source
config_path = Path(sys.argv[1])
workspace = Path(sys.argv[2])
revision = config_path.stem
def swap_workspace_chain():
moved = Path(str(workspace) + ".moved")
os.rename(workspace, moved)
(workspace / "preprocessing").mkdir(parents=True, mode=0o700)
for name, leaf in (("runtime-config", revision + ".yaml"),
("runtime-config-manifests", revision + ".json")):
source = moved / "preprocessing" / name
destination = workspace / "preprocessing" / name
original_inode = os.stat(source).st_ino
original_leaf_inode = os.stat(source / leaf).st_ino
os.rename(source, destination)
assert os.stat(destination).st_ino == original_inode
assert os.stat(destination / leaf).st_ino == original_leaf_inode
try:
_read_runtime_config_source(
config_path,
between_config_and_manifest_traversal=swap_workspace_chain,
)
except ConfigError:
print("rejected")
else:
raise SystemExit("secure reader accepted a replaced workspace chain")
`;
const output = execFileSync(python, ["-c", code, lease.path, workspaceRoot, helper], {
cwd: join(process.cwd(), "..", "harness"),
env: { ...process.env, THT_RUNTIME_CONFIG_MANIFEST_SHA256: lease.manifestSha256 },
encoding: "utf8",
});
expect(output.trim()).toBe("rejected");
} finally { rmSync(f.root, { recursive: true, force: true }); }
});