Guard Linux speech runtime dependency closure and relocatable lookup

This commit is contained in:
karl committed 2026-10-13 21:27:37 +02:00
1 parent 210f2b0136
commit e71cace91f
5 files changed
+80 -1

No files matched your search

+25
View File
@@ -26,3 +26,28 @@ Snapshot: `/home/karl/.cache/searchi-validation/linux-source-9bc7221`.
This closes a client portability check only. It does not prove the Linux native
speech worker, model setup, runtime packaging, recognition quality, desktop UI
or GPU operation. Published Linux downloads remain version 0.1.4.
## Native runtime preparation
A normal auto runtime build from that immutable snapshot is running in
`/home/karl/.cache/searchi-validation/speech-auto-016`; it includes Vulkan
compilation and does not run GPU inference. Do not restart it merely because
an observation times out. Its full compilation and relocation remain unchecked.
Inspection exposed an unverified release path: speech packaging flattened shared
libraries without checking ELF dependency closure or their build-tree lookup
paths. The builder now checks every staged ELF for origin-only RUNPATH and
declared dependencies, allowing a system Vulkan loader only for the dynamic
Vulkan module. Three builder tests passed, including rejection of build-tree
paths, missing libraries and a Vulkan dependency on the CPU worker.
The CMake configuration now gives Linux shared targets their own `$ORIGIN`
RUNPATH; this is required because executable RUNPATH is not transitive.
Actual configuration and a separate native `ggml-base` build passed in
`speech-rpath-check-016`. `readelf` verified that the resulting library has
RUNPATH `$ORIGIN`; generated executable link flags use the same path.
This narrow library check does not establish complete runtime relocation.
The running original snapshot does not contain this correction. Its output
must not be released as corrected evidence. Next: wait for its terminal result,
inspect the complete ELF closure, then validate a corrected normal runtime
outside the build tree with `LD_LIBRARY_PATH` unset and forced CPU recognition.
+9 -1
View File
@@ -33,6 +33,12 @@ else()
set(GGML_METAL OFF CACHE BOOL "" FORCE)
endif()
endif()
if(UNIX AND NOT APPLE AND NOT SEARCHI_INTERNAL_CPU)
# The staged worker and every dynamic backend live together. RUNPATH is
# non-transitive, so each shared target needs its own relocatable lookup.
set(CMAKE_INSTALL_RPATH "$ORIGIN")
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
endif()
add_subdirectory("${WHISPER_SOURCE}" whisper)
target_compile_features(whisper PRIVATE cxx_std_17)
add_executable(searchi-speech-worker worker.cpp)
@@ -44,6 +50,8 @@ endif()
if(MSVC)
target_compile_options(searchi-speech-worker PRIVATE /utf-8)
endif()
if(UNIX)
if(APPLE)
set_target_properties(searchi-speech-worker PROPERTIES BUILD_RPATH "$ORIGIN;@loader_path" INSTALL_RPATH "$ORIGIN;@loader_path")
elseif(UNIX)
set_target_properties(searchi-speech-worker PROPERTIES BUILD_RPATH "$ORIGIN" INSTALL_RPATH "$ORIGIN")
endif()
+5
View File
@@ -20,6 +20,11 @@ inventory files before launch and incorporates the inventory hash in its source
completion fingerprint. A changed backend DLL therefore invalidates reuse.
The builder compiles only; native Linux/macOS loading, full system-dependency
closure, actual GPU speed/fallback and release packaging still require evidence.
Normal Linux builds reject staged ELF files with build-tree RUNPATH or undeclared
dependencies. Each shared target uses `$ORIGIN` for relocation; a system Vulkan
loader is permitted only for the dynamically loaded Vulkan backend. The declared
system C/C++ libraries remain a distribution compatibility requirement. A full
relocated recognition test is still required after compilation.
Windows auto builds reuse the verified Vulkan SDK/loader provisioning and selected
Visual Studio CRT bundling helpers. The isolated runtime inventories its own
`vulkan-1.dll`, full release CRT set and their redistribution notices. Shared helper
+20
View File
@@ -40,6 +40,24 @@ def sha(path):
return result.hexdigest()
def verify_linux_closure(directory):
"""Reject build-tree lookup and undeclared ELF dependencies before release."""
helper = shared_helper("build_runtime.py")
directory = Path(directory)
artifacts = [directory / "searchi-speech-worker"] + sorted(directory.glob("*.so*"))
names = {path.name for path in artifacts}
for path in artifacts:
metadata = helper.elf_dynamic(path)
if metadata.get("RPATH") or metadata.get("RUNPATH") != ["$ORIGIN"]:
raise ValueError(f"Linux speech runtime requires origin-only RUNPATH: {path.name}")
allowed = names | helper.LINUX_SYSTEM_LIBRARIES
if path.name == "libggml-vulkan.so":
allowed = allowed | {"libvulkan.so.1"}
missing = set(metadata.get("NEEDED", [])) - allowed
if missing:
raise ValueError(f"Unpackaged Linux speech dependency for {path.name}: {sorted(missing)}")
def validate_source(source):
revision = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
dirty = subprocess.check_output(["git", "-C", str(source), "status", "--porcelain"], text=True).strip()
@@ -113,6 +131,8 @@ def main():
raise ValueError("Ambiguous speech dependency basename")
shutil.copy2(path, stage / path.name)
records[path.name] = {"path": path.name, "bytes": path.stat().st_size, "sha256": digest}
if args.device == "auto" and sys.platform.startswith("linux"):
verify_linux_closure(stage)
manifest = {"schema": 1, "upstream_commit": UPSTREAM, "device": args.device,
"distribution": args.device == "auto", "source_hashes": source_hashes,
"files": list(records.values()), "native_os_gpu_checks": "unexecuted"}
+21
View File
@@ -28,6 +28,27 @@ class BuildContract(unittest.TestCase):
self.assertTrue(callable(build.shared_helper("build_runtime.py").bundle_windows_crt))
self.assertTrue(callable(build.shared_helper("provision_vulkan.py").provision))
def test_linux_closure_rejects_build_paths_and_missing_dependencies(self):
from types import SimpleNamespace
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in ['searchi-speech-worker', 'libwhisper.so.1', 'libggml-vulkan.so']:
(root / name).write_bytes(b'fixture')
metadata = {'RUNPATH': ['$ORIGIN'], 'NEEDED': ['libwhisper.so.1', 'libc.so.6']}
helper = SimpleNamespace(elf_dynamic=lambda _: metadata, LINUX_SYSTEM_LIBRARIES={'libc.so.6'})
with patch.object(build, 'shared_helper', return_value=helper):
build.verify_linux_closure(root)
metadata['RUNPATH'] = ['/tmp/build']
with self.assertRaisesRegex(ValueError, 'origin-only'):
build.verify_linux_closure(root)
metadata['RUNPATH'] = ['$ORIGIN']
metadata['NEEDED'] = ['libmissing.so']
with self.assertRaisesRegex(ValueError, 'Unpackaged'):
build.verify_linux_closure(root)
metadata['NEEDED'] = ['libvulkan.so.1']
with self.assertRaisesRegex(ValueError, 'Unpackaged'):
build.verify_linux_closure(root)
if __name__ == "__main__":
unittest.main()