Harden worker IPC and recoverable runtime staging

This commit is contained in:
karl committed 2026-10-12 23:46:01 +02:00
1 parent 0bba1a0aa7
commit b3ebd7a894
12 files changed
+1152 -75

No files matched your search

+333 -15
View File
@@ -1,16 +1,327 @@
"""Stage the native worker for installers; optional offline model bundle."""
import argparse
from contextlib import contextmanager
import hashlib
import json
import os
import re
from pathlib import Path
import shutil
import stat
import time
import uuid
ROOT = Path(__file__).resolve().parents[2]
CRT_LIBRARIES = {"concrt140.dll", "msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll", "msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", "vccorlib140.dll", "vcruntime140.dll", "vcruntime140_1.dll", "vcruntime140_threads.dll"}
def _is_link(path):
"""Reject Windows reparse points, including junctions, on Python 3.11+."""
path = Path(path)
if path.is_symlink():
return True
try:
attributes = getattr(path.lstat(), "st_file_attributes", 0)
except FileNotFoundError:
return False
return bool(attributes & getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400))
def _checked_path(path, parent):
"""Validate absolute workspace boundaries before any move or recursive delete."""
path, parent = Path(path).absolute(), Path(parent).resolve()
if _is_link(path) or path.parent.resolve() != parent:
raise RuntimeError("Staging path escapes its checked parent or is a link")
if path.exists() and path.resolve() != parent / path.name:
raise RuntimeError("Staging path resolves outside its checked parent")
return path
def _check_tree(directory):
if _is_link(directory):
raise RuntimeError("Resource staging refuses symlinks or junctions")
count = 0
for parent, directories, files in os.walk(directory, followlinks=False):
for name in [*directories, *files]:
count += 1
if count > 100000:
raise RuntimeError("Resource staging exceeds its file-count limit")
path = Path(parent) / name
if _is_link(path) or not path.resolve().is_relative_to(directory.resolve()):
raise RuntimeError("Resource staging refuses symlinks or junctions")
def _remove_tree(path, parent):
checked = _checked_path(path, parent)
if checked.exists():
_check_tree(checked)
shutil.rmtree(checked)
def _move_directory(source, destination, parent):
"""Bounded retry for transient Windows sharing locks during directory swaps."""
for attempt in range(4):
source = _checked_path(source, parent)
destination = _checked_path(destination, parent)
try:
os.replace(source, destination)
return
except PermissionError:
if attempt == 3:
raise
time.sleep(0.05 * (2 ** attempt))
def _record(path):
path = Path(path)
if _is_link(path) or path.absolute() != path.resolve() or not path.is_file():
raise RuntimeError(f"Staging source is missing or is a link: {path.name}")
with path.open("rb") as stream:
return path.stat().st_size, hashlib.file_digest(stream, "sha256").hexdigest()
def _verified_copy(source, destination, expected=None):
source, destination = Path(source), Path(destination)
expected = expected or _record(source)
if destination.absolute() != destination.resolve():
raise RuntimeError("Staging copy target resolves through a link")
shutil.copy2(source, destination)
if _record(destination) != expected:
raise RuntimeError(f"Staging source changed or copy was incomplete: {source.name}")
return str(destination)
@contextmanager
def staging_lock(parent):
"""Kernel-held lock releases after process crashes; PID is informational."""
path = _checked_path(parent / ".searchi-stage.lock", parent)
with path.open("a+b") as stream:
stream.seek(0, os.SEEK_END)
if stream.tell() == 0:
stream.write(b"\0")
stream.flush()
stream.seek(0)
try:
if os.name == "nt":
import msvcrt
msvcrt.locking(stream.fileno(), msvcrt.LK_NBLCK, 1)
else:
import fcntl
fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as error:
raise RuntimeError("Another resource staging process holds the lock") from error
try:
stream.seek(1)
stream.truncate()
stream.write(f"pid={os.getpid()}".encode())
stream.flush()
yield
finally:
stream.seek(0)
if os.name == "nt":
msvcrt.locking(stream.fileno(), msvcrt.LK_UNLCK, 1)
else:
fcntl.flock(stream.fileno(), fcntl.LOCK_UN)
def _journal_paths(destination, state):
if (not isinstance(state, dict) or state.get("schema") != 1
or state.get("owner") != "searchi-runtime-staging"
or not isinstance(state.get("transaction"), str)
or not re.fullmatch(r"[0-9a-f]{32}", state["transaction"])
or type(state.get("had_destination")) is not bool):
raise RuntimeError("Invalid resource staging recovery journal")
parent = destination.parent.resolve()
transaction = state["transaction"]
return (_checked_path(parent / f".resources.stage-{transaction}", parent),
_checked_path(parent / f".resources.backup-{transaction}", parent))
def recover_staging(destination):
"""Recover interrupted copy/swap while caller holds the staging lock.
This handles process interruption, not certified power-loss durability.
An unsuccessful restoration retains the journal and original backup.
"""
parent = destination.parent.resolve()
destination = _checked_path(destination, parent)
journal = _checked_path(parent / ".searchi-stage.json", parent)
if not journal.exists():
return
if journal.stat().st_size > 4096:
raise RuntimeError("Oversized resource staging recovery journal")
state = json.loads(journal.read_text(encoding="utf-8"))
candidate, backup = _journal_paths(destination, state)
for directory in [destination, candidate, backup]:
if directory.exists():
_check_tree(directory)
marker = ".searchi-stage-complete"
def complete(directory):
path = directory / marker
return path.is_file() and path.stat().st_size == 32 and path.read_text() == state["transaction"]
if not destination.exists():
if backup.exists():
# If restoration fails, do not discard either recoverable directory.
_move_directory(backup, destination, parent)
elif candidate.exists() and complete(candidate):
_move_directory(candidate, destination, parent)
elif state["had_destination"]:
raise RuntimeError("Original staging directory and backup are both missing; recovery retained")
if destination.exists() and backup.exists():
if not complete(destination):
raise RuntimeError("Ambiguous resource staging recovery; original backup retained")
_remove_tree(backup, parent)
_remove_tree(candidate, parent)
if destination.exists():
completion = destination / marker
if completion.exists():
if not complete(destination):
raise RuntimeError("Invalid resource staging completion marker")
completion.unlink()
journal.unlink()
def _previous_owned(destination):
names = {"searchi-gemma-worker.exe", "searchi-gemma-worker", "runtime-libraries.json",
"LLAMA-LICENSE.txt", "VULKAN-LICENSE.txt", "MSVC-REDISTRIBUTION.txt", "vulkan-1.dll"}
manifest_path = Path(__file__).with_name("model-manifest.json")
if manifest_path.stat().st_size > 65536:
raise RuntimeError("Pinned model manifest is oversized")
model_manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
files = model_manifest.get("files") if isinstance(model_manifest, dict) else None
if not isinstance(files, list) or not 1 <= len(files) <= 4:
raise RuntimeError("Invalid pinned model manifest")
for record in files:
name = record.get("path") if isinstance(record, dict) else None
if not isinstance(name, str) or not re.fullmatch(r"[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*\.gguf", name):
raise RuntimeError("Invalid pinned model resource path")
names.add(f"embeddinggemma2/{name}")
inventory = destination / "runtime-libraries.json"
if inventory.exists():
if inventory.stat().st_size > 65536:
raise RuntimeError("Previous runtime inventory is oversized")
previous = json.loads(inventory.read_text(encoding="utf-8"))
records = previous.get("libraries") if isinstance(previous, dict) else None
if not isinstance(records, list) or len(records) > 64:
raise RuntimeError("Invalid previous runtime inventory")
for record in records:
name = record.get("path") if isinstance(record, dict) else None
if not isinstance(name, str) or not re.fullmatch(r"[A-Za-z0-9_-]+\.dll", name):
raise RuntimeError("Invalid previous runtime library path")
names.add(name)
return names
def _resource_key(path):
value = Path(path).as_posix()
return value.casefold() if os.name == "nt" else value
def _preserved_snapshot(destination, owned):
"""Bounded content/tree snapshot detects edits to resources being retained."""
if not destination.exists():
return None
_check_tree(destination)
snapshot = {}
count = 0
for parent, directories, files in os.walk(destination, followlinks=False):
for name in [*directories, *files]:
count += 1
if count > 100000:
raise RuntimeError("Resource staging exceeds its snapshot file-count limit")
path = Path(parent) / name
key = _resource_key(path.relative_to(destination))
if key in owned:
if not path.is_file():
raise RuntimeError("Owned runtime resource is not a file")
continue
if path.is_dir():
snapshot[key] = ("directory",)
else:
record = _record(path)
metadata = path.stat()
snapshot[key] = ("file", *record, metadata.st_mtime_ns, metadata.st_ino, metadata.st_dev)
return snapshot
def stage_resources(destination, sources, validate=None):
"""Replace a verified complete resource directory and preserve other assets."""
destination = Path(destination).absolute()
if destination.parent != destination.parent.resolve():
raise RuntimeError("Staging parent resolves through a link outside its expected location")
destination.parent.mkdir(parents=True, exist_ok=True)
parent = destination.parent.resolve()
destination = _checked_path(destination, parent)
with staging_lock(parent):
recover_staging(destination)
if destination.exists():
_check_tree(destination)
if (destination / ".searchi-stage-complete").exists():
raise RuntimeError("Reserved staging completion marker exists without its journal")
owned = _previous_owned(destination)
owned_keys = {_resource_key(path) for path in owned}
preserved = _preserved_snapshot(destination, owned_keys)
records = []
targets = set()
for source, relative in sources:
relative = Path(relative)
if relative.is_absolute() or relative.anchor or relative.drive or ".." in relative.parts or not relative.parts:
raise RuntimeError("Invalid staging resource target")
key = relative.as_posix().lower()
if key in targets:
raise RuntimeError("Duplicate staging resource target")
targets.add(key)
records.append((Path(source), relative, _record(source)))
state = {"schema": 1, "owner": "searchi-runtime-staging", "transaction": uuid.uuid4().hex,
"had_destination": destination.exists()}
candidate, backup = _journal_paths(destination, state)
journal = _checked_path(parent / ".searchi-stage.json", parent)
pending = _checked_path(parent / f".searchi-stage-journal-{state['transaction']}.pending", parent)
try:
with pending.open("x", encoding="utf-8") as stream:
json.dump(state, stream)
stream.flush()
os.fsync(stream.fileno())
os.replace(pending, journal)
finally:
pending.unlink(missing_ok=True)
try:
if destination.exists():
def ignore_owned(directory, names):
relative = Path(directory).relative_to(destination)
return [name for name in names if _resource_key(relative / name) in owned_keys]
shutil.copytree(destination, candidate, copy_function=_verified_copy, ignore=ignore_owned)
else:
candidate.mkdir()
model_directory = candidate / "embeddinggemma2"
if model_directory.is_dir() and not any(model_directory.iterdir()):
if not model_directory.resolve().is_relative_to(candidate.resolve()):
raise RuntimeError("Owned model directory escapes candidate")
model_directory.rmdir()
for source, relative, expected in records:
target = candidate / relative
if not target.resolve().is_relative_to(candidate.resolve()):
raise RuntimeError("Staging resource target escapes candidate directory")
target.parent.mkdir(parents=True, exist_ok=True)
_verified_copy(source, target, expected)
_check_tree(candidate)
if validate:
validate(candidate)
if _preserved_snapshot(destination, owned_keys) != preserved:
raise RuntimeError("Preserved resources changed during staging; original retained")
(candidate / ".searchi-stage-complete").write_text(state["transaction"])
if destination.exists():
_move_directory(destination, backup, parent)
_move_directory(candidate, destination, parent)
recover_staging(destination)
except Exception:
recover_staging(destination)
raise
def verify_runtime(source, required=False):
"""Preflight an explicit, bounded dependency inventory, never a DLL glob."""
inventory = source.parent / "runtime-libraries.json"
@@ -74,6 +385,12 @@ def main():
parser.add_argument("--backend", choices=["cpu", "auto", "metal", "vulkan", "cuda"], default="auto")
parser.add_argument("--bundle-model", action="store_true")
args = parser.parse_args()
destination = ROOT / "apps/desktop/src-tauri/resources"
if destination.parent.absolute() != destination.parent.resolve():
raise RuntimeError("Staging parent resolves through a link outside its expected location")
destination.parent.mkdir(parents=True, exist_ok=True)
with staging_lock(destination.parent):
recover_staging(destination)
name = "searchi-gemma-worker.exe" if os.name == "nt" else "searchi-gemma-worker"
source = ROOT / ".searchi/runtime" / args.backend
if os.name == "nt":
@@ -91,29 +408,30 @@ def main():
raise RuntimeError("Microsoft CRT redistribution notice is missing")
dependencies.append(crt_notice)
artifacts = []
model_manifest = None
if args.bundle_model:
manifest = json.loads(Path(__file__).with_name("model-manifest.json").read_text())
artifacts = verify_bundle(ROOT, manifest)
model_manifest = json.loads(Path(__file__).with_name("model-manifest.json").read_text())
artifacts = verify_bundle(ROOT, model_manifest)
loader = None
if os.name == "nt" and args.backend in {"auto", "vulkan"}:
from provision_vulkan import provision
_, loader = provision()
destination = ROOT / "apps/desktop/src-tauri/resources"
destination.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination / name)
for dependency in dependencies:
shutil.copy2(dependency, destination / dependency.name)
# Include the upstream runtime license with the statically linked helper.
shutil.copy2(license_path, destination / "LLAMA-LICENSE.txt")
sources = [(source, source.name), (license_path, "LLAMA-LICENSE.txt")]
sources.extend((path, path.name) for path in dependencies)
if loader:
for name in ["vulkan-1.dll", "VULKAN-LICENSE.txt"]:
if name not in {path.name for path in dependencies}:
shutil.copy2(loader / name, destination / name)
if args.bundle_model:
model_destination = destination / "embeddinggemma2"
model_destination.mkdir(exist_ok=True)
for artifact in artifacts:
shutil.copy2(artifact, model_destination / artifact.name)
sources.append((loader / name, name))
sources.extend((path, f"embeddinggemma2/{path.name}") for path in artifacts)
def validate(candidate):
verify_runtime(candidate / source.name, os.name == "nt" and args.backend in {"auto", "vulkan"})
if model_manifest:
for artifact, spec in zip(artifacts, model_manifest["files"], strict=True):
if _record(candidate / "embeddinggemma2" / artifact.name) != (spec["bytes"], spec["sha256"]):
raise RuntimeError("Copied model checksum mismatch")
stage_resources(destination, sources, validate)
print(f"Staged native worker: {destination / source.name}")
+366
View File
@@ -2,6 +2,7 @@
import hashlib
import json
import os
import subprocess
from pathlib import Path
import tempfile
import unittest
@@ -11,6 +12,371 @@ import stage_runtime
class StagingTests(unittest.TestCase):
def resources(self, directory):
root = Path(directory)
destination = root / "src-tauri/resources"
destination.mkdir(parents=True)
(destination / "searchi-gemma-worker.exe").write_bytes(b"previous worker")
(destination / "fonts").mkdir()
(destination / "fonts/Inter.woff2").write_bytes(b"font")
(destination / "media-runtime").mkdir()
(destination / "media-runtime/ffmpeg.exe").write_bytes(b"media")
(destination / "user-note.txt").write_bytes(b"preserve user file")
source = root / "worker.exe"
source.write_bytes(b"next worker")
return destination, source
def test_failed_copy_preserves_previous_complete_resources(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.shutil.copy2
def fail_worker(path, target, *args, **kwargs):
if Path(path) == source:
raise OSError("injected copy failure")
return original(path, target, *args, **kwargs)
with patch.object(stage_runtime.shutil, "copy2", fail_worker):
with self.assertRaisesRegex(OSError, "copy failure"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertEqual((destination / "user-note.txt").read_bytes(), b"preserve user file")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
self.assertEqual(list(destination.parent.glob(".resources.stage-*")), [])
def test_changed_copy_bytes_reject_before_swap(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.shutil.copy2
def corrupt_worker(path, target, *args, **kwargs):
result = original(path, target, *args, **kwargs)
if Path(path) == source:
Path(target).write_bytes(b"bad worker!")
return result
with patch.object(stage_runtime.shutil, "copy2", corrupt_worker):
with self.assertRaisesRegex(RuntimeError, "changed or copy was incomplete"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_source_change_after_preflight_rejects_before_swap(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.shutil.copy2
def mutate_source(path, target, *args, **kwargs):
if Path(path) == source:
source.write_bytes(b"evil worker")
return original(path, target, *args, **kwargs)
with patch.object(stage_runtime.shutil, "copy2", mutate_source):
with self.assertRaisesRegex(RuntimeError, "changed or copy was incomplete"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_resource_targets_reject_traversal_and_windows_drive_roots(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
targets = ["../outside"]
if os.name == "nt":
targets.extend(["C:outside", "\\outside"])
for target in targets:
with self.subTest(target=target), self.assertRaisesRegex(RuntimeError, "resource target"):
stage_runtime.stage_resources(destination, [(source, target)])
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_locked_destination_rename_preserves_old_resources(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.os.replace
def fail_old_rename(path, target):
if Path(path) == destination:
raise PermissionError("destination is locked")
return original(path, target)
with patch.object(stage_runtime.os, "replace", fail_old_rename):
with self.assertRaisesRegex(PermissionError, "locked"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
def test_interrupted_between_renames_restores_old_before_source_preflight(self):
class Interrupt(BaseException):
pass
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.os.replace
def interrupt_candidate(path, target):
if Path(path).name.startswith(".resources.stage-"):
raise Interrupt()
return original(path, target)
with patch.object(stage_runtime.os, "replace", interrupt_candidate):
with self.assertRaises(Interrupt):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertFalse(destination.exists())
self.assertEqual(len(list(destination.parent.glob(".resources.backup-*"))), 1)
with self.assertRaisesRegex(RuntimeError, "source is missing"):
stage_runtime.stage_resources(destination, [(source.with_name("missing"), "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
def test_interrupted_after_install_commits_verified_new_resources(self):
class Interrupt(BaseException):
pass
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.os.replace
def interrupt_after_candidate(path, target):
original(path, target)
if Path(path).name.startswith(".resources.stage-"):
raise Interrupt()
with patch.object(stage_runtime.os, "replace", interrupt_after_candidate):
with self.assertRaises(Interrupt):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
with self.assertRaisesRegex(RuntimeError, "source is missing"):
stage_runtime.stage_resources(destination, [(source.with_name("missing"), "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"next worker")
self.assertEqual((destination / "fonts/Inter.woff2").read_bytes(), b"font")
self.assertEqual((destination / "media-runtime/ffmpeg.exe").read_bytes(), b"media")
self.assertEqual((destination / "user-note.txt").read_bytes(), b"preserve user file")
self.assertEqual(list(destination.parent.glob(".resources.backup-*")), [])
self.assertFalse((destination / ".searchi-stage-complete").exists())
def test_failed_rollback_retains_backup_and_journal_for_retry(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.os.replace
def fail_candidate_and_restore(path, target):
if Path(path).name.startswith((".resources.stage-", ".resources.backup-")):
raise PermissionError("injected rename failure")
return original(path, target)
with patch.object(stage_runtime.os, "replace", fail_candidate_and_restore):
with self.assertRaisesRegex(PermissionError, "rename failure"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
backup = next(destination.parent.glob(".resources.backup-*"))
self.assertEqual((backup / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertTrue((destination.parent / ".searchi-stage.json").exists())
with self.assertRaisesRegex(RuntimeError, "source is missing"):
stage_runtime.stage_resources(destination, [(source.with_name("missing"), "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_profile_switch_removes_only_previous_inventory_libraries(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
old = destination / "ggml-cpu-old.dll"
old.write_bytes(b"old backend")
(destination / "user-plugin.dll").write_bytes(b"user plugin")
(destination / "runtime-libraries.json").write_text(json.dumps({"libraries": [{"path": old.name}]}))
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertFalse((destination / old.name).exists())
self.assertFalse((destination / "runtime-libraries.json").exists())
self.assertEqual((destination / "user-plugin.dll").read_bytes(), b"user plugin")
self.assertEqual((destination / "fonts/Inter.woff2").read_bytes(), b"font")
def test_offline_to_online_removes_only_exact_pinned_model_pair(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
model_directory = destination / "embeddinggemma2"
model_directory.mkdir()
manifest = json.loads(Path(stage_runtime.__file__).with_name("model-manifest.json").read_text())
for spec in manifest["files"]:
(model_directory / spec["path"]).write_bytes(b"old unverified model")
(model_directory / "user-note.txt").write_bytes(b"preserve unrelated asset")
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
for spec in manifest["files"]:
self.assertFalse((destination / "embeddinggemma2" / spec["path"]).exists())
self.assertEqual((destination / "embeddinggemma2/user-note.txt").read_bytes(), b"preserve unrelated asset")
self.assertEqual((destination / "fonts/Inter.woff2").read_bytes(), b"font")
def test_online_staging_removes_empty_owned_model_directory(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
model_directory = destination / "embeddinggemma2"
model_directory.mkdir()
manifest = json.loads(Path(stage_runtime.__file__).with_name("model-manifest.json").read_text())
for spec in manifest["files"]:
(model_directory / spec["path"]).write_bytes(b"old model")
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertFalse((destination / "embeddinggemma2").exists())
def test_old_owned_worker_and_model_pair_are_never_copied(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
model_directory = destination / "embeddinggemma2"
model_directory.mkdir()
manifest = json.loads(Path(stage_runtime.__file__).with_name("model-manifest.json").read_text())
old_paths = {destination / "searchi-gemma-worker.exe"}
for spec in manifest["files"]:
path = model_directory / spec["path"]
path.write_bytes(b"old owned model")
old_paths.add(path)
original = stage_runtime.shutil.copy2
original_record = stage_runtime._record
def reject_owned_copy(path, target, *args, **kwargs):
if Path(path) in old_paths:
self.fail("Previously owned runtime/model asset was unnecessarily copied")
return original(path, target, *args, **kwargs)
def reject_owned_hash(path):
if Path(path) in old_paths:
self.fail("Previously owned runtime/model asset was unnecessarily hashed")
return original_record(path)
with patch.object(stage_runtime.shutil, "copy2", reject_owned_copy), patch.object(stage_runtime, "_record", reject_owned_hash):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"next worker")
self.assertFalse((destination / "embeddinggemma2").exists())
def test_concurrent_preserved_edit_aborts_and_keeps_latest_original(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
def edit_original(_candidate):
(destination / "user-note.txt").write_bytes(b"latest user edit")
with self.assertRaisesRegex(RuntimeError, "Preserved resources changed"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")], edit_original)
self.assertEqual((destination / "user-note.txt").read_bytes(), b"latest user edit")
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
def test_concurrent_preserved_addition_aborts_and_keeps_new_file(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
def add_original(_candidate):
(destination / "fonts/new-font.woff2").write_bytes(b"new user asset")
with self.assertRaisesRegex(RuntimeError, "Preserved resources changed"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")], add_original)
self.assertEqual((destination / "fonts/new-font.woff2").read_bytes(), b"new user asset")
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
def test_concurrent_staging_lock_fails_without_mutation(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
with stage_runtime.staging_lock(destination.parent):
with self.assertRaisesRegex(RuntimeError, "holds the lock"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_malicious_recovery_journal_cannot_escape_sibling_paths(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
outside = Path(directory) / "outside"
outside.mkdir()
(outside / "user-data.txt").write_bytes(b"never remove")
journal = destination.parent / ".searchi-stage.json"
journal.write_text(json.dumps({"schema": 1, "owner": "searchi-runtime-staging", "transaction": "../outside", "had_destination": True}))
with self.assertRaisesRegex(RuntimeError, "recovery journal"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((outside / "user-data.txt").read_bytes(), b"never remove")
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
def test_existing_unrelated_symlink_rejects_before_stage_changes(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
outside = Path(directory) / "outside.txt"
outside.write_bytes(b"private data")
link = destination / "user-link.txt"
try:
link.symlink_to(outside)
except OSError:
self.skipTest("Host does not permit creating test symlinks")
try:
with self.assertRaisesRegex(RuntimeError, "symlinks or junctions"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual(outside.read_bytes(), b"private data")
self.assertTrue(link.is_symlink())
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
finally:
link.unlink()
def test_first_install_interrupt_after_verified_candidate_recovers(self):
class Interrupt(BaseException):
pass
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
destination = root / "src-tauri/resources"
source = root / "worker.exe"
source.write_bytes(b"first worker")
original = stage_runtime.os.replace
def interrupt_candidate(path, target):
if Path(path).name.startswith(".resources.stage-"):
raise Interrupt()
return original(path, target)
with patch.object(stage_runtime.os, "replace", interrupt_candidate):
with self.assertRaises(Interrupt):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertFalse(destination.exists())
with stage_runtime.staging_lock(destination.parent):
stage_runtime.recover_staging(destination)
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"first worker")
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
@unittest.skipUnless(os.name == "nt", "Windows junction boundary regression")
def test_existing_junction_rejects_without_following_outside_directory(self):
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
outside = Path(directory) / "outside"
outside.mkdir()
(outside / "private.txt").write_bytes(b"untouched")
link = destination / "user-junction"
result = subprocess.run(["cmd.exe", "/c", "mklink", "/J", str(link), str(outside)], capture_output=True)
if result.returncode:
self.skipTest("Host does not permit creating a test junction")
try:
self.assertTrue(stage_runtime._is_link(link))
with self.assertRaisesRegex(RuntimeError, "symlinks or junctions"):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((outside / "private.txt").read_bytes(), b"untouched")
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
finally:
# Remove only the verified test junction, never its target tree.
self.assertEqual(link.absolute().parent, destination.absolute())
self.assertTrue(stage_runtime._is_link(link))
link.rmdir()
def test_interrupted_partial_candidate_keeps_old_resources_and_recovers(self):
class Interrupt(BaseException):
pass
with tempfile.TemporaryDirectory() as directory:
destination, source = self.resources(directory)
original = stage_runtime.shutil.copy2
def interrupt_copy(path, target, *args, **kwargs):
if Path(path) == source:
Path(target).write_bytes(b"partial")
raise Interrupt()
return original(path, target, *args, **kwargs)
with patch.object(stage_runtime.shutil, "copy2", interrupt_copy):
with self.assertRaises(Interrupt):
stage_runtime.stage_resources(destination, [(source, "searchi-gemma-worker.exe")])
self.assertEqual((destination / "searchi-gemma-worker.exe").read_bytes(), b"previous worker")
with stage_runtime.staging_lock(destination.parent):
stage_runtime.recover_staging(destination)
self.assertEqual(list(destination.parent.glob(".resources.stage-*")), [])
self.assertFalse((destination.parent / ".searchi-stage.json").exists())
@unittest.skipUnless(os.name == "nt", "Windows app-local CRT staging")
def test_missing_crt_notice_preserves_existing_staged_worker(self):
with tempfile.TemporaryDirectory() as directory: