ADR-0038: Build identity discoverability in the shipped artifact
Date: 2026-06-30 / Status: Accepted (no wire-contract / golden / ABI change; radiates the existing FMF_BUILD_VERSION / fmf-buildstamp identity to the artifact surface)
Context
The dev / nightly / stable build lanes were complete (ADR-0035): one format authority (xtask version) computes a channel-aware string, CI exports it as FMF_BUILD_VERSION, and the Rust binaries (fmf-buildstamp::VERSION) and the C# app (InformationalVersion) stamp it. The single source of truth was clean — but it did not reach the surface of a downloaded artifact. A user who downloaded a build could not tell, at a glance, which channel/version it was:
- The zip filename was the only signal, and it is lost the moment the zip is extracted (the bundle folder is always
FindMyFiles,paths.rs). - No in-bundle version file — the bundle shipped only an instructional
README.txt, with no version/channel/commit/date. - The root
FindMyFiles.exe(the launcher) carried no real version resource —winresourcedefaulted it to the internal crate namefmf-launcherat a static0.1.0.0, identical across all channels (misleading). - No in-app version display — the GUI showed only the engine version (F12, pipe-only); the app’s own version was reachable only via the F12 “copy diagnostics” dump.
SHA256SUMS.txtwas non-standard — a bare uppercase hash with no filename (mirroring PowerShellGet-FileHash), so the ubiquitoussha256sum -c SHA256SUMS.txtcould not verify it.- Inconsistencies in the version identity itself:
fmf diagreported the bareCARGO_PKG_VERSION(no channel/sha), disagreeing withfmf --version; the C#InformationalVersioncarried Source Link’s full 40-char sha with nogprefix and even leaked+shainto stable, diverging from the Rust+g<7>/ clean-stable shape.
Decision
Radiate the existing build identity (no new version source) to four artifact surfaces, following industry-standard mechanisms, and fix the identity inconsistencies so every surface agrees.
- In-bundle
BUILDINFO.txt(the strongest at-a-glance, survives extraction).xtask publishwrites a Notepad-friendly, grep-ablekey: valuefile (product, version, channel, commit, date, source, license) besideREADME.txt. The version uses the same precedence as the binaries (FMF_BUILD_VERSIONelse local-dev+g<sha>); the date is the git commit date (reproducible, no wall clock), with the nightly’s embedded date preferred. Parsing/rendering is pure and unit-tested inxtask/src/version.rs(parse_identity/render_buildinfo). - Launcher Win32 VERSIONINFO.
fmf-launcher/build.rssets the resource viawinresource: numericFileVersion = X.Y.Z.0(Win32 requiresa.b.c.d) and stringProductVersion = FMF_BUILD_VERSION(the channel-aware value), plusProductName=FindMyFiles, description, copyright and source URL — so Explorer → Properties → Details identifies the build without running it. - In-app About / version block. The Settings dialog’s Status section shows the app version (always, selectable to copy) and the engine version (pipe mode), and raises a warning InfoBar when their
X.Y.Zbases differ (BuildInfo.SameBase) — surfacing a stale app/service pairing that nothing previously detected. - Standardised release artifacts.
SHA256SUMS.txtmoves to coreutils format (lowercase hash, two spaces, filename), directory-driven overbuild/package, verifiable withsha256sum -c. The nightly Actions artifact is named with its date (find-my-files-nightly-<date>). - Identity consistency.
fmf diagnow reportsfmf_buildstamp::VERSION(matches--version). The C# side disables Source Link’s auto-append (IncludeSourceRevisionInInformationalVersion=false) and constructs+g<short7>itself via an MSBuild target, exactly mirroringxtask version(+g<7>; stable stays clean).
The change flow stops short of the contract: fmf-contract / fmf-proto / contract/golden are untouched (no wire/ABI/golden change).
Rationale
- Radiate, don’t add a source. Every surface derives from the one
FMF_BUILD_VERSION/ fmf-buildstamp value; the format authority remainsxtask version. This preserves the ADR-0035 single-source discipline — no surface can drift. BUILDINFO.txtover relying on the zip name. The filename is the strongest signal until extraction, after which a plain-text file is the only thing that survives — and it doubles as machine-readable (key: value), consistent with the project’s logfmt direction (ADR-0037).- coreutils
SHA256SUMSis the de-facto standard. No stable release had shipped (.release-please-manifest.json=0.0.0), so there were no consumers of the old uppercase/no-filename shape to break — the right moment to standardise. - Mismatch detection is cheap and real. Both sides already stamp the same fmf-buildstamp shape, so comparing the
X.Y.Zbase is trivial and catches a genuine support-time problem (which app is talking to which service).
Trade-off
The launcher’s dev fallback (~5 lines: FMF_BUILD_VERSION else -dev+g<sha>) is duplicated in fmf-launcher/build.rs and fmf-buildstamp/build.rs. Build scripts cannot share a runtime const, and a shared leaf crate for five lines is over-engineering; the duplication is annotated with a cross-reference and the format authority stays in xtask version. The C# short-sha is resolved at MSBuild target-execution time (the sha is unknowable at property-evaluation time); when git is absent (source tarball) it falls back to the channel tag without a sha, mirroring the Rust None branch. Local C# dev builds append .dirty under the same tree-dirtiness rule as the Rust/xtask identity.
Rejected alternatives
- A shared
fmf-buildmetaleaf crate used as a build-dependency by both build scripts: rejected — the only real duplication is the 5-line dev fallback, and parsing lives once inxtask; a crate to dedupe five lines fails the dsa-first cost test. - Naming the extracted bundle folder with the version (so the folder itself signals the build): rejected — the zip stores contents at the root (matching the historical
Compress-Archiveshape), andBUILDINFO.txtcovers the post-extraction case without restructuring the archive. - Embedding SBOMs in
SHA256SUMS.txt: deferred — SBOMs are attached as release assets and used as predicates in the ZIP’s SBOM attestations; they are not themselves build-provenance subjects.SHA256SUMSstays directory-driven overbuild/package, so a future SBOM dropped there is covered automatically. - Structured
version=/channel=logfmt fields on every line: deferred — the version is already on the launch line; first-class fields are a refinement, not part of artifact discoverability, and would touch the freshly-landed logfmt infra.