* test(search,daemon,storage): assert behaviour instead of frozen literals
Several suites encoded exact internal values that legitimate tuning and
ranking changes are allowed to move: SCIENTIFIC-era profile weights and
rrfK values, corpus-statistic figures, and hardcoded timeout/cancellation
numbers. They failed on every platform after those changes landed, so the
real behaviour regressions they should catch were hidden behind noise.
Replace the frozen literals with contract assertions: weights are finite,
non-negative and normalize, state selection still holds, capability-gated
sources stay zeroed, and hybrid results keep matching canonical hash/path
sets. Deadlines are not widened and the daemon paths they cover are
unchanged.
Also add independent corpus and adapter oracles, deterministic collector
cancellation and late-result gates, and synchronize ServiceManager
shutdown observation, so the tests fail for the behaviour they name.
The portability allowlist keeps its two reviewed raw test-env reads; only
their line numbers moved.
Signed-off-by: trvon <git@trevon.dev>
* ci(tests): summarise Meson failures without flooding the log
A failing test run printed the whole Meson suite output, so the actual
failing test names and their assertion messages were buried and the
uploaded logs were dominated by passing-test chatter.
Add a bounded reporter that extracts the failing test names plus their
surrounding diagnostics, keeps an overall cap on emitted lines, and is
unit-tested against representative Meson output. Wire it into the Tests
workflow so failures stay readable.
Signed-off-by: trvon <git@trevon.dev>
* build(deb): validate candidate identity, mapping and upgrade ordering
The Debian packaging scripts took filenames and versions on trust, so a
misnamed candidate or a wrong package ordering could pass validation while
the real install path was never exercised.
Fail closed on candidate identity, version and architecture, verify the
package-to-source mapping, seed the legacy-only layout, wait on the actual
services, and assert install/upgrade/remove ordering for both Debian and
RPM in an offline container. Cases that cannot be proven on a host are
reported as skipped rather than silently passing.
Signed-off-by: trvon <git@trevon.dev>
---------
Signed-off-by: trvon <git@trevon.dev>
The package scripts only addressed user managers that had a D-Bus user
bus socket (/run/user/<uid>/bus). Debian and Ubuntu without
dbus-user-session have none, so upgrades did not reload or restart running
user daemons and removal left them running on deleted binaries. Detect a
manager by its private socket (/run/user/<uid>/systemd/private), which
systemctl --user uses directly, and pass only XDG_RUNTIME_DIR.
Signed-off-by: trvon <me@trevon.dev>
The service is now user-bound: each user runs their own daemon with
their own config ($XDG_CONFIG_HOME/yams/config.toml), corpus
($XDG_DATA_HOME/yams) and socket ($XDG_RUNTIME_DIR/yams-daemon.sock),
which is exactly what the CLI already resolves. The system service, its
yams account/group, the group socket and the /etc/yams/config.toml
configuration go away.
- Unit: /usr/lib/systemd/user/yams-daemon.service runs
`yams-daemon --foreground` with default path resolution,
Restart=on-failure, WantedBy=default.target, and light hardening
(NoNewPrivileges, LockPersonality, RestrictRealtime, RestrictSUIDSGID,
native syscalls only). No ProtectHome/PrivateTmp: the daemon reads the
files its user adds.
- Enablement: /usr/lib/systemd/user-preset/80-yams.preset plus
`systemctl --global preset` on first install (and on upgrade from a
release with the system unit), so the unit starts with every user's
next session; administrators override with a user preset or
`systemctl --global disable`, and later upgrades keep that choice.
Package scripts never start per-user instances: they reload running
user managers, try-restart running user daemons on upgrade and stop
them on removal (`systemctl --user` via runuser per /run/user/<uid>),
then `systemctl --global disable` on removal.
- Migration from 0.20.x and the system-service design: deb preinst, rpm
%pre and Arch pre_upgrade stop and disable the old system unit while
its file is still installed (deb also purges deb-systemd-helper
state). /var/lib/yams, the old system corpus that the CLI never
reached, is left alone and documented; user corpora are never
touched.
- The sysusers entry, account creation and the deb's passwd and
init-system-helpers dependencies are dropped; the deb purge no longer
deletes /var/lib/yams.
- Install paths move to lib/systemd/user and lib/systemd/user-preset in
meson, the Arch stage/PKGBUILD and the install-surface policy.
- docs/install.md: the user service, enabling it now
(`systemctl --user enable --now`), lingering, plugins via the user's
config, opting out, upgrade and removal behaviour, and the 0.20.3
leftover.
Signed-off-by: trvon <me@trevon.dev>
The deb postinst created the account with systemd-sysusers and fell back
to `adduser`, which the package never depended on. In a minimal Debian or
Ubuntu container (no systemd, no adduser) installation stopped in
postinst with "adduser: not found" and left the package half-configured.
The postinst still prefers the shipped sysusers.d entry, then falls back
to groupadd/useradd from `passwd`, now a declared dependency (Priority:
required, so it costs nothing). If neither works it prints what to do,
skips enabling the service and exits 0: the CLI does not need the account,
so a missing account no longer breaks the install. The rpm %post gets the
same groupadd/useradd fallback when systemd-sysusers cannot run; Arch
already relies on pacman's sysusers hook plus an explicit
systemd-sysusers call.
Signed-off-by: trvon <me@trevon.dev>
rpm's dependency generator advertised every ELF library in the package
as a system soname, including the bundled ONNX Runtime
(libonnxruntime.so.1()(64bit)), so the yams RPM could satisfy other
packages' ONNX Runtime dependencies, and it emitted requires on the
private libyams_onnx_resource/libzpdf it ships itself. Filter provides
from /usr/lib/yams and drop requires on those private sonames.
Signed-off-by: trvon <me@trevon.dev>
rpm expands macros inside comments. The generated spec's comment named
%systemd_postun_with_restart, which systemd-rpm-macros defines with
required arguments, so rpmbuild on Fedora 42 (rpm 4.20 with the macros
installed) stopped with "The %systemd_postun_with_restart macro
requires some arguments". Debian and Ubuntu builders do not define the
macro, which is why release CI never saw it. Write the names as %% in
the comments, including the %postun mention in the %post scriptlet.
Checked with rpmbuild -bb of the generated spec in fedora:42 with
systemd-rpm-macros: the previous spec fails, this one builds and its
%post carries the systemd-sysusers call.
Signed-off-by: trvon <me@trevon.dev>
The packaged yams-daemon.service ran under DynamicUser= and its
ExecStartPost chmod'ed /run/yams/yams-daemon.sock to 0666, so every
local account could read, add and delete documents in the system
corpus. It also used the deprecated PermissionsStartOnly= and a light
sandbox (ProtectSystem=full only).
The unit now runs as a static `yams` system account created from
/usr/lib/sysusers.d/yams.conf (deb postinst, rpm %post and the Arch
install script run systemd-sysusers before starting it). The daemon's
own socket mode (owner rwx, group rw) with Group=yams and UMask=0007
gives a group-only socket, and /run/yams, /var/lib/yams and
/var/log/yams are 0750, so access is granted by adding a user to the
`yams` group. The chmod and PermissionsStartOnly are gone.
Sandboxing: ProtectSystem=strict, ProtectHome, PrivateTmp,
PrivateDevices, empty CapabilityBoundingSet, kernel/clock/hostname/
cgroup protection, ProtectProc=invisible, RestrictAddressFamilies
(unix, inet, inet6, netlink), RestrictNamespaces, LockPersonality,
RemoveIPC and a @system-service syscall filter without @privileged and
@resources. MemoryDenyWriteExecute stays off for ONNX Runtime.
Package scripts: Arch pre_remove now disables as well as stops (it left
a dangling multi-user.target.wants link), and deb purge removes
/var/lib/yams and /var/log/yams, including the /var/{lib,log}/private
copies earlier DynamicUser= releases left. Upgrades keep the corpus:
systemd moves the DynamicUser= state back to /var/lib/yams on first
start under User=yams.
docs/install.md describes the group, the client socket order, and what
the sandboxed service can and cannot read.
Signed-off-by: trvon <me@trevon.dev>
- macOS zips were built with `zip -r`, which follows symlinks: bin/yams
(-> yams-cli) shipped as a second 34 MB copy and each versioned dylib
link became another full file. `zip -ry` stores the links.
- RPMs used rpm's gzip-class default payload (37.5 MB vs 28.6 MB for the
zstd .deb of the same tree); the spec now sets w19.zstdio (rpm >= 4.14).
The spec's License also said Apache-2.0.
- The Arch PKGBUILD and the CI .PKGINFO generator declared Apache-2.0
(the project is GPL-3.0-or-later) and depended on liburing, libarchive,
taglib, sqlite, openssl, curl and boost-libs, none of which the
installed binaries load: readelf shows they need only libstdc++,
libgcc_s, libc and libm, so depends is gcc-libs and glibc.
- packaging/appimage was never built by any workflow; it and the
*.AppImage globs in release.yml and the release manifest go away.
- packaging/homebrew/yams.rb was a stale CMake-era formula; release.yml
renders the tap formula from yams.rb.template, which stays.
Signed-off-by: trvon <me@trevon.dev>
Release used buildtype=debugoptimized (-O2 -g, strip=false) and relied on
scripts/prune-runtime-install.sh to strip the staged payload, discarding
the debug info. Crash reports from shipped binaries could not be
symbolicated and the build type label did not match what shipped.
- setup.sh/setup.ps1 Release and the Meson project default use
buildtype=release. setup.sh adds -Ddebug=true for Release so -O3 code
keeps DWARF for the split, and passes -Db_pie=true explicitly (also a
project default) so reconfigured build dirs pick it up. Windows keeps
release without -g so no PDBs land in the MSI stage. Debug/Fuzzing are
unchanged (-O0 -g, assertions on).
- scripts/split-debug-symbols.sh replaces prune-runtime-install.sh: ELF
files get objcopy --only-keep-debug (compressed) into <debug-dir>/
<path>.debug plus .build-id/xx/yyyy.debug links, strip
--strip-unneeded and --add-gnu-debuglink; Mach-O files get dsymutil
dSYMs and strip -S -x. Without a debug dir it only strips.
- The release job splits into $BUILD_DIR/debug-symbols and uploads it as
a debug-symbols-<suffix> workflow artifact (30 days) instead of
shipping or dropping it. build-deb.sh, the Dockerfile, PKGBUILD and
build-arch-pkg.sh call the split script in strip-only mode.
Measured on grim (Linux x86_64, clang 21, stripped staged payload):
file debugoptimized release -O3 delta
bin/yams-cli 35,528,968 36,733,304 +3.4%
bin/yams-daemon 29,731,312 30,685,792 +3.2%
bin/yams-mcp-server 19,504,928 19,865,472 +1.8%
libyams_onnx_resource.so 477,280 481,488 +0.9%
libyams_object_storage_s3.so 8,226,608 8,239,016 +0.2%
yams_zyp.so 79,936 80,040 +0.1%
Debug symbols: 309 MiB (compressed .debug files); gdb resolves
`info line main` on the stripped daemon through the .build-id tree.
check_install_surface: no debug sections or .symtab remain; the
remaining violations are the export leaks fixed later in this series.
Signed-off-by: trvon <me@trevon.dev>