For the final two-distro checks and current results, use the AppImage desktop acceptance record.
This recipe packages the production addict executable with GTK 4.20 and
libc. It leaves dictionary indexes in the normal shared application data
path, $XDG_DATA_HOME/addict (default ~/.local/share/addict). OCR,
updates, publication and complete dependency notices are later
work. Do not distribute this experimental artifact as a finished release.
The Flatpak recipe and application behavior are unchanged.
Use an x86_64 Linux host with rootless Podman, Bash, Git, Python 3.11 or later, GNU tar/coreutils and HTTPS access to Fedora, GitHub and crates.io. Allow several GiB of disk space and memory for the release build; the notices added by 5.4 need further hosts and space, listed under its Prerequisites below. Rootless Podman must already work; no privileged container or desktop bus is needed to build. No Rust installation or FUSE is needed on the host.
packaging/appimage/inputs.lock.json pins the Fedora 43 OCI digest, every
RPM in the resolved package closure (including the Rust compiler and Cargo)
and the quick-sharun, sharun, cross-libc-dlopen and appimagetool downloads.
Each download is SHA-256 checked, including cached inputs. The Containerfile
installs only those local RPMs with repositories disabled. No mutable latest
lookup or replacement input is allowed. Cargo uses Cargo.lock, a fresh
Cargo home and a fresh target directory; crate checksums are checked by Cargo.
The lock records exact inputs, not a claim of byte-for-byte reproducible output.
Fedora 43 is the build environment, as planned. Its newer bundled libc and
GTK are intentional: sharun starts the executable with the bundled loader.
Fedora Mesa and LLVM are retained (I_WANT_FAT_APPIMAGE=1) to include
software rendering instead of mixing in another distribution's driver build.
The GLES library and Vulkan CPU driver (lavapipe) are explicit runtime inputs;
the checker and refusal fixtures require them, including their ICD metadata.
This increases image size; GPU/kernel compatibility on the older target
desktop still requires the acceptance below. Fontconfig links are dereferenced
when staged so they cannot escape the bundle. The bundle opts into
USE_HOST_XDG_CACHE_HOME=1, preserving shared application cache paths as
well as configuration and data paths.
The output component and RPM inventories are inputs to the later notices
story; they do not replace that story's license audit.
git clone https://github.com/doqhu/addict.git
cd addict
scripts/build-appimage.shThe launcher snapshots tracked and nonignored source files into a temporary build directory, records its revision, source archive checksum and dirty status, builds the pinned container without layer reuse, and runs exactly:
cargo build --release --locked -p addict-app --bin addictIt uses no test features, existing target directory or existing Cargo cache.
It caches only checked external inputs under target/appimage-inputs.
For an entirely uncached run, remove that directory before building.
Outputs appear under target/appimage/ only after inspection succeeds:
Addict-<version>-x86_64.AppImageand.sha256;.build.txt: revision, source checksum/status, compiler, Cargo and lock hashes;.inputs.lock.json,.rpms.txt,.components.sha256and.layout.txt: locked inputs, installed packages, all bundled file hashes and inspected layout.
cd target/appimage
sha256sum --check Addict-*.AppImage.sha256
cd ../..
scripts/check-appimage.sh target/appimage/Addict-*.AppImage
packaging/appimage/tests.shThe checker uses the runtime's extraction command, without FUSE. It checks
ELF architecture, loader, GTK/libc, schemas, fonts, icons, modules, symlink
containment and dictionary/OCR data contamination (including renamed indexes
with the ADDICTIX header). Refusal tests use disposable layout files, not
published dictionary fixtures. These checks do not prove a visible window or
translation on a desktop.
Failures name their step and input/component. Pinned RPMs are fetched from the
signed copies that Fedora's Koji keeps for each build, so a package update does
not remove them. If one is still missing, restore that exact checked file or
deliberately review a new lock; do not switch to a current package silently.
Bad cached checksums fail; remove only the named cached input and retry. Set
ADDICT_APPIMAGE_KEEP_WORK=1 to retain the temporary source/build workspace
for diagnosis; each later build still starts with a fresh directory. Existing
outputs are cleared before the first check of a build, so a failed rebuild
cannot report an old image as new.
Quick-sharun stages the real executable at shared/bin/addict; bin/addict
and AppRun are sharun launchers. Libraries live under lib, data under
share, and configuration under etc. .layout.txt records the actual
validated tree. Do not use this description as a substitute for process evidence.
The pinned appimagetool contains uruntime 0.8.1 and DwarFS. The launcher
changes its single embedded URUNTIME_EXTRACT=3 setting to =2 before
checksumming: automatic extraction after failed mounting is unrestricted by
the default 350 MiB size cap. It preserves ordinary launch and does not force
APPIMAGE_EXTRACT_AND_RUN. The checker explicitly extracts only for inspection.
See the pinned quick-sharun source
and uruntime source.
Run this on a disposable Ubuntu 24.04 LTS or Debian 13 full desktop VM
(GNOME or KDE), with no FUSE device available. A headless display or a host
Fedora window is useful diagnostics but does not satisfy this acceptance.
Record /etc/os-release, desktop/version, kernel, ls -l /dev/fuse, image
SHA-256 and the build metadata. Transfer the image, its inventories and
scripts/check-appimage.sh to the VM, into one directory.
Use a fresh absolute profile and private desktop bus; do not invoke addict uninstall against your normal profile. In a VM terminal:
image=$(realpath ./Addict-*-x86_64.AppImage)
profile=$(mktemp -d)
mkdir -p "$profile"/{home,data,config,cache,runtime}
chmod 700 "$profile/runtime"
if [ -n "${WAYLAND_DISPLAY:-}" ]; then
ln -s "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$profile/runtime/$WAYLAND_DISPLAY"
fi
env -u APPIMAGE_EXTRACT_AND_RUN -u APPIMAGE_TARGET_DIR -u TARGET_APPIMAGE \
-u DBUS_SESSION_BUS_ADDRESS -u GTK_PATH -u GTK_MODULES -u LD_LIBRARY_PATH \
HOME="$profile/home" XDG_DATA_HOME="$profile/data" \
XDG_CONFIG_HOME="$profile/config" XDG_CACHE_HOME="$profile/cache" \
XDG_RUNTIME_DIR="$profile/runtime" \
dbus-run-session -- "$image" > "$profile/launch.log" 2>&1 &The Wayland socket link above uses the VM compositor (keep WAYLAND_DISPLAY
set). On X11 keep DISPLAY and pass the VM's absolute XAUTHORITY path when
launching, since the fresh home has no authority file. Use the
VM desktop's socket, never a host socket. The private bus prevents forwarding
to a resident instance. Keep the profile directory for the evidence.
Observe the window. Use the existing first-run or Settings dictionary download
flow over HTTPS, installing a bilingual source such as FreeDict English to
Russian (freedict.index). Record its source/edition, filename, SHA-256 and installed absolute
path under $profile/data/addict, outside every image/extraction path. Select
English, type a known word such as cat, and record the actual Russian result
(for example кот/кошка) and a screenshot. Leave the DeepL key unset. A
network download performed outside the application does not establish this check.
Find the resident addict PID in this private bus/profile. Before exiting,
run ./check-appimage.sh --trace <pid> "$profile/evidence" to
save its maps, executable, command line, parent process chain and packaging
environment. Check that mapped libgtk-4, libc.so.6 and loader paths belong to the
bundle's extracted tree, not the VM's older GTK/libc. Record APPIMAGE,
APPDIR, ARGV0/ARG0 when present, and /proc/<pid>/exe literally.
Confirm which variable still names the persistent outer .AppImage file
after extraction; do not infer it from the staged tree or assume that
current_exe identifies the outer file. This evidence is needed by later
startup/update stories. Close the app and record exit/cleanup behavior. The
close button only hides the resident, and tray Exit is unavailable on the
private bus, so end it with kill -TERM <pid> and then check that the
appimage_extracted_* tree is gone.
addict install is explicit: it installs a visible launcher pointing at the
canonical persistent outer APPIMAGE, with no background flag. The Settings
startup switch uses the same target with --background through the Background
portal. Nothing automatically installs or repairs a launcher. Keep the image
at that location. After moving it, run the image's install command from its
new location, then turn startup off and on in Settings to replace the portal's
saved command. Deleting or moving the image cannot automatically repair a
command that already points to its old location. Disable and uninstall do not
need to resolve the old image target.
For a repeatable relaunch check on the disposable target desktop, Python 3 with PyGObject/Gio must be available as verification tooling (they are not application dependencies):
packaging/appimage/tests.sh --relaunch IMAGE NEW_EVIDENCE_DIRThis copies the image into images with spaces under the new evidence
directory, installs against disposable HOME/XDG directories and uses a private
bus with no service activation directories. Its mock implements Registry and
Background only; only that mock writes/removes the disposable autostart entry.
It never contacts the desktop's actual portal. Follow the current instruction
in stage.json or the terminal:
- Observe the normal window, dismiss the optional dictionary dialog, open Settings and enable Start at login. The harness captures the real request.
- After it stops the resident and proves the extraction tree disappeared,
it launches the installed desktop entry again. Observe the normal window
and create
NEW_EVIDENCE_DIR/launcher-confirmed. - After another complete cleanup, it launches the portal-written desktop
command. Confirm that no app window appeared and create
NEW_EVIDENCE_DIR/background-confirmed. - Open Settings (the app's
settingsaction is available on its private bus), turn startup off and on, and let the harness stop the resident and uninstall.
To open Settings for the silent resident without first opening its main window, use the private bus address recorded by the harness:
DBUS_SESSION_BUS_ADDRESS="$(cat NEW_EVIDENCE_DIR/bus-address)" \
gdbus call --session --dest io.github.doqhu.Addict \
--object-path /io/github/doqhu/Addict \
--method org.gtk.Actions.Activate settings '[]' '{}'Each observation is bounded to five minutes. PASS is written only after
both commands have launched fresh bundled residents after cleanup, the
Settings requests were true/false/true, and uninstall requested false before
removing its launcher. Preserve screenshots for normal/silent observations,
portal.json, saved-command.json, cleanup.log, the process traces and
image checksum. This check uses an isolated mock portal; it does not certify
the target desktop's actual login session or portal implementation.
Persistent relaunch acceptance, recorded 2026-11-11:
- Fresh pinned build from revision
9fe8bc24e125a19326b1025d1e4c8f127f260c2aplus the uncommitted 5.2 Rust changes. Artifact SHA-256:c15a5292ca7802f3ddcaa945906265778b8f60cb64653928250e2795d8c39220. Source snapshot SHA-256:5e5013045ff30d43ad8487705a62f356d5c27ab16e849cff20b15aab375aba05. Extraction/layout checks and packaging refusal fixtures passed. - Disposable snapshot of the earlier Ubuntu 24.04.5 GNOME desktop, kernel
6.8.0-147-generic, no
/dev/fuse. The new image was copied to/home/tester/relaunch-5.2/images with spaces/Addict-0.1.1-x86_64.AppImage. All HOME/XDG paths and the Registry/Background mock bus were private. - Explicit install saved the outer image, without flags. Desktop-entry
launch produced resident PID 2444 and a visible normal window. Settings
enable registered and sent the quoted outer image plus
--background. After SIGTERM, the resident and its/tmp/appimage_extracted_Addic1555832400808022495tree disappeared. - Launching the installed desktop entry after cleanup produced fresh resident
PID 2734 and the visible normal main window. After another confirmed cleanup,
launching the portal-written entry produced PID 2935 with no app window.
The three process traces show the extracted executable and persistent
APPIMAGE; the reused extraction pathname had been absent between runs. - Settings off was confirmed as
disabled; its portal command was empty. The subsequent on request again named the persistent image. After final resident/extraction cleanup, uninstall registered while the launcher still existed, sent an empty disable command, and removed the launcher, icons and saved state. Portal request sequence: true, false, true, false. Harness PASS. - Evidence:
target/appimage-relaunch-5.2/contains the guest log and process traces, portal payloads, three cleanup records, SHA-256/host records, and screenshots of the initial window, launcher relaunch, silent background relaunch and disabled switch. The final harness additionally retains a launcher snapshot and waits for the final on state to be saved; those two small verification improvements were added after this recorded run. scripts/check.sh, focused startup/entries/install/uninstall tests,packaging/appimage/tests.sh, shell/Python syntax andgit diff --checkpassed. Private-bus tests require running outside the restricted sandbox. Real desktop login startup and real portal behavior were not exercised.
Post-review final artifact, verified again on 2026-11-11:
- Review found that a mode-bit check could accept an image the current user
could not execute (owner mode
0641). The resolver now usesfaccessat(X_OK, AT_EACCESS); the exact refusal case is covered by the isolated installation test, preserving all existing profile files. - Rebuilt after that fix: artifact SHA-256
76b159f838597f6ac32695fa6d3f5a7d303d4d9f9d1915ba43f063b0a279cfcb; source snapshot SHA-25680d1825b6baf32f65f951e0b1c8b58ddf093b3d11bd6aa1b78d572ca8bf4ed6e.scripts/check.sh, packaging refusals and extraction validation passed again. - Parent repeated the complete desktop harness with the final code and harness
on a new disposable Ubuntu snapshot. Explicit install used
/home/tester/relaunch-5.2-final/images with spaces/Addict-0.1.1-x86_64.AppImage. Initial/launcher/background residents were PIDs 2427, 3074 and 3354./tmp/appimage_extracted_Addic2048419049103469335was confirmed absent after each stop, before the next launch. The launcher opened a normal window; the portal command launched silently. Settings off/on and uninstall passed, with portal sequence true/false/true/false. Final harness result: PASS. - Evidence:
target/appimage-relaunch-5.2-final/, including launcher text, portal payloads, checksums, process traces, cleanup records and screenshots. The VM was powered off. This final run retains the same private mock-portal limitation: real desktop login and the system portal were not exercised.
Desktop acceptance passed on 2026-11-10 on a disposable Ubuntu 24.04 GNOME VM (record below). Repeat it on the target desktop for each new artifact; do not record a headless launch as passing it.
A repeatable launch/trace diagnostic is also available as
packaging/appimage/tests.sh --runtime IMAGE NEW_EVIDENCE_DIR; it uses the
caller's display with a fresh profile and private bus, records paths, then
closes the diagnostic instance. It cannot certify visible UI or translation.
Recorded 2026-11-10:
- Cold build: fresh source, Cargo home, target and uncached container build;
baseline revision
1ec2fd795ac008f3be38edeaadafd0f217e8d3b5, with these packaging changes uncommitted. Rust/Cargo 1.99.1; GTK 4.20.4; 384 pinned RPMs. The exact final launcher completed from fresh source/Cargo/target directories, followed by extraction and runtime checks. - Final artifact:
Addict-0.1.1-x86_64.AppImage, SHA-25642c6b33f0179bc7ee8fe239f5f651b948ebda133662fe676aa8aef18fb85b9b5. SHA-256 verification, artifact extraction/layout validation, shell syntax, refusal fixtures,git diff --checkandreuse lintpassed. - Runtime diagnostic: Ubuntu 24.04.4 container, pinned toolbox digest
8db97043cc5f028a64d00d604c627b86a839a493a4d3d9ae2e9b2d6755e49fac, with a private bus/profile, no/dev/fuseor GPU device, displaying through the Fedora 45 host's GNOME 51.1 Wayland socket. This is not full target desktop acceptance. The ordinary invocation unset extraction controls; the log shows failed FUSE followed by successful automatic extraction. uruntime itself setAPPIMAGE_EXTRACT_AND_RUN=1after that fallback. - Observed executable:
/tmp/appimage_extracted_Addic17395649457895943886/bin/addict.APPDIRnamed that temporary root. Mapped GTKlib/libgtk-4.so.1.2000.4, libclib/libc.so.6, loaderlib/ld-linux-x86-64.so.2and GLESlib/libGLESv2.so.2.1.0all belonged to it. The staged real binary remainsshared/bin/addict; the process reports thebin/addictalias. Neither is the persistent outer file. APPIMAGEandARG0both named/artifact/Addict-0.1.1-x86_64.AppImage, the persistent outer file. HOME and XDG data/config/cache paths were all checked against the fresh profile and preserved exactly; index storage would be/evidence/clean-verified-run/data/addict, outside the image.- Local raw trace:
target/appimage-runtime-ubuntu24/clean-verified-run/(log, host record and process maps/executable/parents/environment). Bundle resources and file hashes are in the emitted.layout.txtand.components.sha256. Expected private-container warnings reported missing accessibility, portals, tray host and physical GPUs; the resident survived and the library/path assertions passed without forcing a renderer. - The container diagnostic above installed no dictionary and claims no translation; the desktop acceptance below does.
Desktop acceptance, recorded 2026-11-10:
- Target: disposable KVM guest from the signed Ubuntu 24.04 cloud image
(
noble-server-cloudimg-amd64.img, SHA-25604e8f2461a16a44e78a370284471f29616cdd9baf1893df25807b71f985b6c94, SHA256SUMS signature from the UEC Image Automatic Signing Key) plusubuntu-desktop-minimal: Ubuntu 24.04.5 LTS, kernel 6.8.0-147-generic, GNOME Shell 46.0 on Wayland with automatic login. Its own GTK is 4.14.5 and glibc 2.39. FUSE was unavailable: no/dev/fuse,fusermount3mode 0000. - Image: the SHA-256 file above verified in the guest. Launched with the
procedure above, verbatim, from the guest's GNOME session. The log shows
failed to utilize FUSE during startup!then automatic extraction; no extraction variable was set by the caller. - Window: the main window and the first-run "Download dictionaries" dialog
appeared on the GNOME desktop. Its "Download" installed all 13 offered
indexes from release
indexes-v3over HTTPS.freedict(English to Russian; manifest asset SHA-25632f2f6064b96528e483693a5b8d9b9dd2e6af2aef2288a190899a5d505db8bb0) was installed as$profile/data/addict/freedict.index, SHA-256511d72bbac3d94b5b217d3f99a916649bcd2106d0484b7bf27e1a09e9524439a, outside the extraction tree, which held no.indexfile. - Translation: with EN selected and no DeepL key, typing
catshowedкóшкаwith alternativesкошкаandкот, marked "Offline dictionary". - Process:
/proc/<pid>/exewas/tmp/appimage_extracted_Addic17395649457895943886/bin/addictandAPPDIRnamed that root. Mappedlibgtk-4.so.1.2000.4,libc.so.6,ld-linux-x86-64.so.2,libGLESv2.so.2.1.0andlibglib-2.0.so.0.8600.5all came from it; no system GTK or libc was mapped.APPIMAGEandARG0both named/home/tester/Addict-0.1.1-x86_64.AppImage: the persistent outer file signal isAPPIMAGE. uruntime setAPPIMAGE_EXTRACT_AND_RUN=1itself. - Expected limits of the isolated run: the shortcut banner reported that GNOME
46 has no
org.freedesktop.host.portal.Registry, and the tray could not register on the private bus. Startup and desktop integration belong to later stories. - Exit: the close button hid the window and the resident kept running, as
designed. Tray Exit was unavailable on the private bus, so the resident was
ended with SIGTERM. Within 34 seconds the runtime processes exited and the
extraction tree was removed; an empty
/tmp/appimage_extracted_Addic17395649457895943886.lockfile remained. The installed indexes stayed in the profile. - Koji URLs: after the lock moved to Koji, a build from a fresh clone with an
empty input cache fetched and verified all 388 inputs and validated a new
artifact (SHA-256
1b1e24976307db514dd43609cd832d9521b97796fde50f89c09c8d29c2c18655). Its installed RPM list is identical to the accepted artifact's, but five bundle files differ (shared/bin/addict,lib/libp11-kit.so.0.4.11, two GStreamer helpers, a sharun hook), so builds are not bit-for-bit reproducible. The desktop acceptance above applies to the42c6b33f…artifact. - Raw evidence:
target/appimage-runtime-ubuntu24-desktop/(screenshots, launch log, process trace, host record, exit record, release manifest and the guest's cloud-init configuration).
The pinned Fedora 43 input set adds Tesseract 5.5.3-2.fc43, Leptonica 1.87.0-1.fc43 and their resolved RPM dependencies (391 RPMs total). The three models and Apache license use the same tessdata_fast 4.1.0 URLs and SHA-256 pins as the unchanged Flatpak manifest. The build verifies every RPM, tool, model and downloaded license before use, and installs with repositories and weak dependencies disabled. There is no mutable download fallback.
Build and inspect with:
scripts/build-appimage.sh
packaging/appimage/tests.sh
scripts/check-appimage.sh target/appimage/Addict-0.1.1-x86_64.AppImage
(cd target/appimage && sha256sum --check Addict-0.1.1-x86_64.AppImage.sha256)The refusal fixtures require the verified input cache populated by the build.
The final layout has executable bin/tesseract (the sibling launcher) and
shared/bin/tesseract (engine), bundled lib/libtesseract.so*,
lib/libleptonica.so* and their complete ELF dependency closure. Only
share/tessdata/{eng,spa,cat}.traineddata are allowed anywhere in the image:
| Model | SHA-256 |
|---|---|
| eng | 7d4322bd2a7749724879683fc3912cb542f19906c83bcc1a52132556427170b2 |
| spa | 6f2e04d02774a18f01bed44b1111f2cd7f3ba7ac9dc4373cd3f898a40ea6b464 |
| cat | 250db73cd5b380d2798581295dc12f20d0828cdb335a65d833d12dfdbf57117d |
Quick-sharun's implicitly copied share/tesseract and share/tessdata
contents are replaced explicitly. share/ocr-components.sha256 records the
packaged engine and every recursively resolved ELF dependency; validation
checks those contents as well as model pins and executable paths before build
success metadata is emitted. The normal complete file/RPM inventories remain.
Upstream engine, Leptonica, giflib and tessdata notices are retained under
share/licenses/ocr. This is not the complete 5.4 notice audit.
On a disposable Ubuntu 24.04 desktop without host Tesseract, model trees or
/dev/fuse, install verification tooling only:
sudo apt-get install python3-gi python3-pyatspi at-spi2-core strace dbus-x11
packaging/appimage/tests.sh --ocr /absolute/path/Addict-0.1.1-x86_64.AppImage \
/absolute/path/new-ocr-evidenceUse the desktop session's actual DISPLAY and XAUTHORITY (omit the latter
assignment when inherited correctly); GNOME Xwayland is supported. Transfer
scripts/check-appimage.sh, packaging/appimage/{tests.sh,ocr.py,inputs.lock.json}
and packaging/appimage/fixtures/*.png with their relative repository paths.
Python, Gio, AT-SPI, strace and D-Bus are verification tools, not application
requirements. Missing tools and host OCR contamination produce named refusals.
The harness first validates the supplied image. For each language it creates
fresh HOME/XDG directories and a private session bus without system portal
activation. Only its private Screenshot/Registry mock and the standard AT-SPI
service are available. There are no OCR configuration overrides; the normal
language preference selects EN, ES or CA. AT-SPI presses the production Capture
button and reads the resulting GTK field. The production app receives the
known PNG URI from Screenshot, performs the existing capture/OCR path, and
removes the snapshot. The known texts, including Spanish and Catalan accents,
are asserted exactly. It records successful model opens with strace and actual
child executable/arguments/loaded-library paths through /proc, then bounds
resident/extraction teardown. PASS.json appears only after all three outputs
and cleanup succeed. A reused evidence directory is refused.
Fixtures are verification resources and never enter the bundle. Regenerate
with python3 packaging/appimage/fixtures/generate.py (Pillow and Liberation
Sans 2.1.5; font SHA-256
76d04c18ea243f426b7de1f3ad208e927008f961dc5945e5aad352d0dfde8ee8).
The checked PNGs make repeated verification independent of renderer/font
availability on the target machine.
This verifies packaged capture and OCR with a deterministic Screenshot response. It does not exercise real area selection, the desktop's Screenshot portal implementation, or the final two-distribution acceptance of 5.7.
Executed OCR verification, 2026-11-11:
- Fresh production build from baseline revision
48fdcd07baf26526bed4ddb46a72a111ff810827with the 5.3 packaging changes; source snapshot98f039dcb1b6bc3ccb56af922d7cba41f46167fcbd087ba57cd7e5b715fc73e0. AppImage SHA-256:b65a029d4c2dd173a61b18fae9fc995d07548116a6fed47ce62476ea39d8317f. The final fixture/harness refinements are verification resources outside the bundle. No Rust or Flatpak changes were needed. - Disposable overlay of the existing Ubuntu 24.04.5 GNOME desktop base,
kernel 6.8.0-147-generic. The base was not modified. Only verification
packages were added to the overlay; it had no host Tesseract, system
tessdata tree or
/dev/fuse. Xwayland displayed the normal production UI. Each launch log records failed FUSE followed by automatic extraction. - The final command,
tests.sh --ocr IMAGE /home/tester/ocr-5.3-verified, passed all three exact GTK field assertions:The bank is beside the river.,El niño está cerca del río., andEl camí és a prop del riu.. Actual OCR child PIDs were 5077, 5166 and 5320, each executing/tmp/appimage_extracted_Addic11528054946601516958/bin/tesseractwith--tessdata-dir .../bin/../share/tessdataand its matching-l eng/spa/cat. Successful model opens identify the canonical bundled model per language. Every observed child shared-library mapping belonged to the extracted bundle, including Tesseract, Leptonica and libc. - Each resident terminated and its extraction tree was confirmed absent
before the next language started. The runtime reused the same extraction
pathname only after that absence check.
PASS.jsonwas written after all three cleanup checks; the VM was powered off after collecting evidence. - Earlier diagnostics are not acceptance: an AT-SPI startup-cache race timed
out before Capture, and the first Spanish fixture's
pingüinowas read aspinguino. The harness now clears accessible-node caches; the final Spanish fixture usesniñowhile retaining exact accented expectations. Those failed runs emitted no PASS. Their logs are retained with the final evidence. - Evidence:
target/appimage-ocr-5.3/ocr-5.3-verified/contains the image/host records, private bus definition, each profile's actual text, production application evidence, strace calls, child maps, fixture hashes and cleanup records. The archive in its parent directory also retains diagnostic logs. The checked PNGs were rendered with Pillow 12.3.0 and the font pin above. - Packaging refusal fixtures passed for missing/corrupt pinned inputs,
missing/corrupt engine/Leptonica/models, unexecutable launchers,
extra/relocated models, dictionary headers/extensions, symlink escapes and
wrong ELF architecture. Final artifact extraction validation and SHA-256
verification, shell/Python syntax,
git diff --checkandreuse lintpassed.
Post-review final artifact, independently verified 2026-11-11:
- Review reproduced a missing-SONAME case: deleting
lib/libtesseract.so.5.5left its payload present and initially escaped validation. The OCR inventory now retains required dependency pathnames as well as resolved files. Both the refusal fixture and deletion from the real final extracted image fail withmissing OCR dependency: lib/libtesseract.so.5.5. - A fresh build after that correction passed all package checks. Artifact SHA-256
e9873c9977296aeccae7ab131f3f0d097f92dffe47a25d2dee6db1e2ef6f6f56; source snapshot SHA-256cd03c52c34872e771ea13132c1f12937f71b17147aab91d06f1186ad65cad482. - A rebooted VM exposed an accessibility-tool initialization race before Capture. The unbundled harness now activates its private accessibility bus, queries the Registry root and selects that explicit address before importing pyatspi or launching the app. This uses no retry and preserves all assertions.
- The parent rebooted the Ubuntu overlay again and reran the complete final
harness. English, Spanish and Catalan exactly matched the texts above;
actual OCR child PIDs were 2211, 2305 and 2469. Successful model opens and
child library maps belonged to
/tmp/appimage_extracted_Addic15801695028444076955; the extraction tree was confirmed absent between all launches and after the last one. - Final evidence:
target/appimage-ocr-5.3/ocr-5.3-parent-reboot/and its.tgzarchive, includingPASS.json, image checksum, host record, traces, actual UI text and cleanup. The VM was powered off. Shell/Python syntax, refusal tests, final image extraction/checksum, whitespace and REUSE checks passed. The deterministic Screenshot and two-distro acceptance limitations remain as described above.
The build attributes every regular file and symlink of the AppDir, and the
AppImage runtime header, to a component, installs that component's license
texts and writes a machine-readable inventory,
share/licenses/components.json, which is also kept beside the artifact as
<name>.notices.json. packaging/appimage/notices.py runs inside the pinned
container after quick-sharun's deployment and before AppDir validation.
Besides the hosts listed above, the build downloads from
kojipkgs.fedoraproject.org (source RPMs), raw.githubusercontent.com,
deb.debian.org (the dash tarball), static.crates.io,
static.rust-lang.org, ziglang.org, ftp.gnu.org, www.gnu.org and
musl.libc.org. The first build fetches about 2.3 GB of source RPMs and
0.3 GB of other sources into target/appimage-inputs/; each build copies
them once more into its temporary workspace (mktemp -d, usually /tmp) and
once into target/appimage/<name>.sources/, so allow about 8 GB beyond the
earlier build. The refusal tests (packaging/appimage/tests.sh) need no
rpm on the host: they give build-time attribution a fixture owner map.
Attribution is derived from the pinned build environment, not a hand-kept list:
-
Fedora files: the installed RPM headers (
rpm -qafile names, license flags,SOURCERPMandLICENSE) own an AppDir path through this prefix map, the first owned candidate winning, also through resolved system symlinks (usrmerge, xkb):AppDir prefix System prefixes, in order shared/bin//usr/bin/,/usr/libexec/,/usr/libexec/gstreamer-1.0/bin//usr/bin/,/usr/libexec/lib/gstreamer-1.0//usr/libexec/gstreamer-1.0/lib//usr/lib64/,/usr/lib/share//usr/share/etc//etc/Paths, not digests, are compared, since quick-sharun rewrites deployed files. An RPM bump therefore re-derives the components.
-
Explicit rules cover only the project's own files, relocated notices (
share/licenses/ocr/<rpm>/), files of the pinned tools (sharunand its copies asAppRunandbin/*launchers,dash,lib/sharun-preload/*), quick-sharun's generated layout (AppRun.sh,AppRun.lib,.env,bin/*.hook,lib/lib.path,shared/lib,etc/ssl/openssl.cnf) and two caches (gschemas.compiled-> glib2, Adwaitaicon-theme.cache-> adwaita-icon-theme). -
A file that matches neither fails the build before any success metadata, naming the path (
unattributable file: ...).
Each component has an origin, an NEVRA or pinned inputs, an SPDX expression,
its license texts under share/licenses/<component>/ and a source record;
files maps every AppDir path to its component. The SPDX expression is the
RPM LICENSE tag for Fedora packages; addict's is MIT plus every license
heading of packaging/licenses/crate-licenses.txt, read at build time.
Components whose binary RPM has no %license file take the texts of an
installed sibling package built from the same source RPM, else the upstream
license files of the pinned source RPM itself (%license names and
top-level COPYING/LICENSE-style files of its source archives). libdrm and
libglvnd ship no license file upstream: their texts are the license header
of xf86drm.c and libglvnd's README.md licensing section, read from the
pinned source RPMs. No text comes from a generic template when the
component's own text exists.
The prebuilt tools carry static code of their own. Each library linked into
them is a component (origin: static, embedded_in naming the tool), with
its own license texts and pinned source; the tool's component records how
its closure was determined (static_closure). The record is
packaging/appimage/static-closures.json, generated by
packaging/appimage/closures.py and committed; rerun it after bumping
uruntime/appimagetool, Anylinux-sharun or DwarFS and review the diff. Only
regenerating it needs host cargo (it sets RUSTC_BOOTSTRAP=1 to read the
tools' nightly manifests) and network access; the build itself does not.
- Runtime header: uruntime 0.8.1, variant
appimage-dwarfs-lite(its only embedded helper isdwarfs-fuse-extract, whose payload checksum matches uruntime'schecksums.txt). Its Rust crates come from uruntime's pinnedCargo.lockwith those features forx86_64-unknown-linux-musl(normal dependencies; proc-macro crates and other build-time code excluded); its standard library from therust-srcof the nightly the header's.commentnames (2026-09-24); musl 1.2.5 and compiler_rt from Zig 0.16.0, its linker. - DwarFS helper:
dwarfs-fuse-extract0.15.7 links boost, libarchive, LibreSSL, libfuse3, liblzma, zstd, xxHash, jemalloc, fmt, nlohmann/json, utfcpp, range-v3, parallel-hashmap, the GCC 16.1.0 runtime (libstdc++, libgcc) and musl 1.2.6, at the versions DwarFS's own static build pins (.docker/static-libs-versions.sh,toolchain-versions.shin the pinned DwarFS source), each confirmed by the binary's strings. lz4, brotli, flac, ricepp, fuse2, mimalloc, OpenSSL and the stack-trace libraries are not in it; the reasons are recorded instatic-closures.json. sharun3.8.3: the pinned source has noCargo.lock, so its crates are resolved withcargo generate-lockfile --publish-timeat the time of the 3.8.3 release run and checked against its actual x86_64 compilation log; std from the pinned nightly-2026-09-11rust-src; musl and compiler_rt from Zig 0.16.0, which also builds the preload libraries. The upstream job records the exact checkout, all 22 selected runtime crate versions, compiler, archive checksum and binary checksum. Normalized evidence is committed inpackaging/appimage/sharun-provenance.json; its archive and binary hashes match the pinned download. The log confirms only the eight-characteruserland-execvegit revision prefix; the full source revision remains pinned ininputs.lock.json. Retrieve the raw log withgh api repos/pkgforge-dev/Anylinux-sharun/actions/jobs/127034383534/logs --allow-escape-sequencesand compare its SHA-256 to the evidence record. The generator refuses to write an inventory whose sharun crate versions or input identity disagree; this includes both Rust toolchain components (stdand compiler-builtins) and their checksum-pinned nightly sources. To recheck the committed inventory and cached inputs without network access:packaging/appimage/closures.py --check-sharun-provenance target/appimage-inputs.dash0.5.13.5: musl and compiler_rt from Zig 0.16.0. dash names GPL-2.0-or-later (the bash-derivedmksignames.coutput) without its text; the GNU GPL-2.0 text is pinned from gnu.org and installed with its notice.
License texts come from the pinned sources themselves (crate packages,
release tarballs, rust-src, Zig's lib/libc/musl/COPYRIGHT). A package
without a license file of its own (memfd-exec) gets the SPDX license-list
text, pinned at v3.29.0. The standard library's backtrace crates (addr2line,
gimli, object, miniz_oxide, adler2, memchr) are listed although
panic = "immediate-abort" may drop them, since the pinned binaries cannot
show whether they are linked.
The lock gained three arrays, verified like every other input:
srpms: the Koji source RPM (URL and SHA-256) of every Fedora package that contributes a file, mounted read-only at/srpmsduring the build.notices: license texts and source archives of the non-Fedora components: uruntime 0.8.1, DwarFS 0.15.7, Anylinux-sharun 3.8.3 (sharun, preload libraries, and the patch/build script of its staticdash), the dash 0.5.13.5 release tarball (sha512 matches Anylinux-sharun's build script), quick-sharun's repository license and cross-libc-dlopen v0.2.10.static: the sources and texts of the statically linked components above (crates with theCargo.lockchecksum as SHA-256, git dependency archives,rust-src, Zig, the DwarFS libraries, the DwarFS source with its build scripts, GNU and SPDX texts), mounted read-only at/static.
After the artifact passes validation, the launcher assembles every pinned
source the inventory names, with a SHA256SUMS, as
target/appimage/<name>.sources.part and renames it to
target/appimage/<name>.sources/ only after the artifact and its metadata
are in place: beside the AppImage, never inside it, for epic 6 to publish
with each release. A source the inventory names that is not pinned, or a
Fedora component without a pinned source RPM, refuses the build. Check with:
(cd target/appimage/Addict-0.1.1-x86_64.AppImage.sources && sha256sum --check --strict SHA256SUMS)scripts/check-appimage.sh trusts the inventory only after checking it
against the image, and fails, naming the item, when:
- a file or symlink in the image is not in the inventory, or the inventory names a file the image lacks;
- a component's license file is missing, empty or outside the image;
- a GPL, LGPL, MPL (or other copyleft) component has no pinned source record, any source record differs from its lock entry, or a Fedora component's source is not one of the pinned source RPMs;
- the runtime header lacks uruntime, dwarfs or any library
static-closures.jsonlists for them (missing notice: runtime header (<component>)), or, for an artifact, the header is not uruntime; - a tool in the inventory (sharun, dash) lacks a library of its static closure, or a static component's SPDX expression differs from the record.
It prints the component and file counts. The existing addict, crate,
Tesseract, Leptonica, giflib, tessdata and dash notices stay where they were;
the inventory references them. packaging/appimage/tests.sh has a refusal
fixture for each case, plus build-time attribution of an unowned file.
Known limits: the SPDX expressions of tool and DwarFS-library components are taken from their upstream license files and repositories, and the texts installed are the controlling ones. sharun's reconstructed crate versions are confirmed against the actual release build (see above). The GCC start files linked into cross-libc-dlopen (Debian's GCC 10) are not listed separately; they fall under the GCC runtime library exception. The 5.6 sweep retains the GStreamer, cdparanoia and bubblewrap payload and its notices: removing dynamically loaded GTK/media helpers would require additional runtime acceptance. Their complete source/notices handoff remains part of this bundle.
- Fresh
scripts/build-appimage.shfrom baseline revision3c169fbc32912217088d9938b5eec6cebb3c99a6with the 5.4 changes (source snapshotaffca6d07a5e77e33f94f2dff04b936c934678d4fcdd7d625a72fce6a10182bb). AppImage SHA-25636aa4dd6c2e0004272b54e5299bfa236c9cfda753d5536a44ab43d5a78755a2a. - The build reported
Notices: 233 components (70 statically linked), 2619 files; the build's and a separate run ofscripts/check-appimage.shon the artifact both reportedNotices: 233 components (62 in the runtime header) cover 2619 files. License texts came from the binary RPM for 103 components, an installed sibling for 22 and the pinned source RPM for 30; 70 are statically linked libraries, 8 project, tool and runtime components. target/appimage/Addict-0.1.1-x86_64.AppImage.sources/holds 195 sources plusSHA256SUMS(124 source RPMs; 2.5 GB in all);sha256sum --check --strictpassed. The lock pins 125 source RPMs: ca-certificates contributed onlyetc/ssl/openssl.cnf, which quick-sharun writes itself, so it is not a component.- Rerunning
packaging/appimage/closures.pyleft the lock andstatic-closures.jsonbyte-identical. packaging/appimage/tests.shpassed, including the refusals: unlisted file, removed and empty notice, copyleft component without source, changed source pin, Fedora component pinned to a binary RPM, missing uruntime, dwarfs, libfuse3 and procfs-core runtime-header components, sharun without its static closure, stale inventory entry and build-time unattributable file (with a fixture owner map, no hostrpm). Shell syntax,git diff --checkandreuse lintpassed.
Post-review final artifact, 2026-11-11: after the review fixes (tool binaries
must belong to their tool components, a project revision counts as a copyleft
source record), a fresh build from source snapshot
f26d267afb371609b1ccc36908d819886fbf4538d0b229d89e8a3dfe0665907b produced
AppImage SHA-256
4ab5f3ea487658056e86c491fa506b0da2d650202a97fb20fa8951d7c28f0888. The build
and a separate scripts/check-appimage.sh run on it reported the same 233
components (62 in the runtime header) covering 2619 files; its 195 sources
passed sha256sum --check --strict. tests.sh also passed the new
misattributed-sharun refusal.
The independent appimage job in .github/workflows/ci.yml runs on an
Ubuntu 24.04 host. It preserves the other jobs and uses the same pinned Fedora
build, verified input lock, fresh Cargo/build directories and production binary
as the maintainer command. It removes unused runner SDKs to allow space for the
temporary build and the roughly 2.5 GiB corresponding-source set; it does not
cache target. Allow at least 20 GiB free for this job, including container
storage, inputs, temporary copies, outputs and the final uncompressed tar.
Run the same checks locally on an x86_64 Linux host with working rootless Podman:
scripts/build-appimage.sh
packaging/appimage/tests.sh
packaging/appimage/smoke-tests.sh
version=$(python3 -c 'import tomllib; print(tomllib.load(open("Cargo.toml","rb"))["workspace"]["package"]["version"])')
scripts/check-appimage-smoke.sh "target/appimage/Addict-$version-x86_64.AppImage" target/appimage-smoke
scripts/archive-appimage.sh target/appimage-smoke target/appimage-handoffBoth new output directories must not already exist: use a new name for another
attempt. The smoke builds packaging/appimage/smoke.Containerfile (Ubuntu 24.04
at a fixed OCI digest; Ubuntu's verification-tool packages use its repositories).
Only preparing this verification image uses network access. Execution uses
private PID/IPC namespaces, --network=none, a read-only root and checkout/image
mounts, a disposable executable /tmp, and a writable diagnostics mount. No
host display socket, session/accessibility bus, engine/model directory or FUSE
device is mounted. The existing ocr.py creates each language's private
HOME/XDG paths and buses, uses Xvfb with X authentication and exercises the
native Capture button and actual GTK field. It traces the bundled OCR child,
models and libraries, checks exact EN/ES/CA output and waits for extraction
cleanup after each language. Container exit removes any processes or temporary
extraction even when the harness fails or times out.
The container grants only SYS_PTRACE for strace and process evidence, retaining
the default seccomp policy; an explicit ptrace probe fails before OCR if the
runner disallows tracing. container-build.log, container.log and ocr/
(including language logs, syscalls, UI results, bus configuration and cleanup)
remain in diagnostics on failure. ocr/PASS.json is created only after all
languages and cleanup pass. A failed command prevents handoff assembly/upload;
Actions retains any smoke diagnostics as appimage-smoke-failure-<attempt> for
seven days. The focused wrapper tests cover container build/run failure,
missing success evidence, reused output directories and failed handoff.
They also refuse missing/corrupt corresponding sources, including a source
removed together with its checksum line.
The version comes exclusively from [workspace.package].version in
Cargo.toml. For version 0.1.1, the Actions artifact is named
Addict-0.1.1-x86_64-handoff, scoped to its workflow run/attempt and commit.
It contains Addict-0.1.1-x86_64-handoff.tar and its relative .tar.sha256.
The tar preserves executable mode, contains no enclosing directory, and holds:
Addict-0.1.1-x86_64.AppImageand.AppImage.sha256(relative image name);.AppImage.build.txt,.AppImage.inputs.lock.json,.AppImage.rpms.txt,.AppImage.layout.txt,.AppImage.notices.json,.AppImage.components.sha256;.AppImage.sources/, the complete 5.4 corresponding-source set and its relativeSHA256SUMS(currently 195 source files).
The assembler rechecks the image, package/license audit, sidecar notice inventory against the embedded one, bundled component checksums, input lock and every source against the pinned inventory. It rejects a missing output, a changed checksum/source/notice, or smoke evidence for a different image before writing the tar. Sources cannot be dropped by deleting their checksum line as well. The upload action uses an immutable revision, fails on missing files, and applies no additional compression to these already compressed payloads.
Download the handoff artifact from the successful run and extract its Actions ZIP first. Verify and extract the contained tar in a fresh directory:
sha256sum --check --strict Addict-0.1.1-x86_64-handoff.tar.sha256
mkdir unpacked
tar -xf Addict-0.1.1-x86_64-handoff.tar -C unpacked
cd unpacked
test -x Addict-0.1.1-x86_64.AppImage
sha256sum --check --strict Addict-0.1.1-x86_64.AppImage.sha256
(cd Addict-0.1.1-x86_64.AppImage.sources && sha256sum --check --strict SHA256SUMS)Epic 6 consumes these relative image, checksum, metadata and corresponding-source
paths together, using .build.txt to identify the source revision/snapshot and
the workflow run to identify CI provenance. Actions artifacts expire after seven
days. The appimage job itself creates no GitHub Release, publishes no release
assets and uploads no dictionaries; the release workflow publishes this
handoff, checked again, as release assets, which release.md
lists. Its deterministic Screenshot portal is a smoke test; real desktop
screenshots, portal UX, dictionary downloads, typed translation,
desktop/login and two-distribution human acceptance remain 5.7 work. Local
runner-equivalent results are not evidence that hosted Actions has executed.
The exact upstream sharun job confirmed all 22 recorded runtime crate
versions and the pinned archive/binary hashes. Regeneration retained every
static component definition, source pin and byte of inputs.lock.json;
only the host's determination text changed. Provenance refusal fixtures,
existing package/wrapper refusals, Python/Bash syntax, REUSE and whitespace
checks passed.
A fresh pinned container/release build from the local source snapshot
b472f53eac0e93dcb04311ad5962756bbe28ab05b7ab5a5962b109b1504ae578
produced AppImage SHA-256
23a550ffdb5f930019b95ff58159ca1a6fe834c0705fcbbead51c563dd971a9a.
The build metadata records the working-tree changes over its baseline;
this was a local source snapshot build. Package audits passed with 233
components, 2619 files and 195 corresponding sources. Bundle membership,
sharun, the Tesseract executable and all three models match final 5.5;
byte-identical rebuilds are still not promised.
Ubuntu 24.04.5 container smoke passed exact English/Spanish/Catalan output,
bundled child/model/library paths and extraction cleanup with no host OCR,
models or FUSE. Evidence: target/appimage-smoke-5.6/ocr/. The checked
handoff is in target/appimage-handoff-5.6/; extraction preserved executable
mode, and tar, image and all source checksums passed. Raw upstream evidence
is retained at target/appimage-provenance-5.6/upstream-job.log.
This is local verification; hosted Actions and 5.7 desktop/login acceptance
remain unobserved by this sweep.
After Quick review, the provenance check also requires the recorded std and
compiler-builtins components and their exact pinned nightly sources.
Changed/missing toolchain components and a wrong source now have executed
refusal coverage. Regeneration and all checks passed again. A second full
build produced final AppImage SHA-256
0092a70feb306953a28188d2113b06740de9e930971ba5991dab2c42541c7557,
source snapshot
ab2f0c4638d3596ef06f6a56e38ba4cbd476e6ecd95a8dc6a8f871e8a25af270.
Final package audit, three-language production OCR, bundled paths and
cleanup passed. target/appimage-handoff-5.6-final/ passed archive transfer,
executable mode and every image/source checksum; smoke and transfer
evidence are under target/appimage-smoke-5.6-final/ and
target/appimage-unpacked-5.6-final/.