From fc5cba15e176ca721e67d8535e8f88d27d959fb6 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Fri, 21 Aug 2026 04:57:56 +0000 Subject: [PATCH] feat(dirty-content-fingerprint): Nix flake computes fingerprint via self.narHash Adds myDirtyFingerprint to flake.nix's existing eval-time let block, computed as builtins.hashString "sha256" self.narHash truncated to 8 hex chars (self.narHash is SRI base64, not hex -- rehashing is necessary), gated on the same clean/dirty branch myCommitId already uses. No preConfigure build-phase hook needed -- self.narHash has no pure-eval trap unlike build_time's builtins.currentTime. Threaded through nixSupport/default.nix into xce.nix/xcecli.nix's eval-time mesonFlags as -Ddirty_fingerprint=, alongside the completely untouched -Dcommit_id=/ myCommitId and build_time's separate preConfigure mechanism. Verified independently by review with real nix build/nix log inspection on both xcecore_debug and xcecli: real, non-empty, content-sensitive values reach mesonConfigurePhase, cross-checked deterministic via an independent Python recomputation, with commit_id fully unaffected. Full artifact completion is blocked by a pre-existing, unrelated sandbox network limitation (confirmed recurring on two disjoint Go module sets), not caused by this change. Co-Authored-By: Claude Sonnet 5 --- .../specs/dirty-content-fingerprint/tasks.md | 9 ++---- src/flake.nix | 29 ++++++++++++++++++- src/nixSupport/default.nix | 10 +++++-- src/nixSupport/xce.nix | 5 ++++ src/nixSupport/xcecli.nix | 5 ++++ 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/.kiro/specs/dirty-content-fingerprint/tasks.md b/.kiro/specs/dirty-content-fingerprint/tasks.md index 67d0f0fe..eaa06427 100644 --- a/.kiro/specs/dirty-content-fingerprint/tasks.md +++ b/.kiro/specs/dirty-content-fingerprint/tasks.md @@ -12,12 +12,9 @@ - _Requirements: 1.1, 1.2, 1.3, 1.4, 5.1_ - _Boundary: xce-build.sh_ -- [ ] 2.2 (P) Nix flake computes the fingerprint via self.narHash at eval time, independently of myCommitId - - `myCommitId`'s existing computation (including its `self.dirtyShortRev`-derived `-dirty` suffix) is **not touched** -- this task only adds a new, independent value alongside it - - `src/flake.nix`'s existing eval-time `let` block gains `myDirtyFingerprint`, derived from `self.narHash` (truncated to match the dev-loop path's format), computed the same way `myCommitId` already is -- **not** via a `preConfigure` build-phase hook, since `self.narHash` (unlike `build_time`'s `builtins.currentTime`) is a legitimate flake self-introspection field with no pure-eval trap; resolves to empty on a clean tree, or a real hash otherwise - - `src/nixSupport/default.nix` forwards `myDirtyFingerprint` into each `callPackage` call, alongside the existing `myCommitId` - - `src/nixSupport/xce.nix`, `src/nixSupport/xcecli.nix` accept the new argument and append `"-Ddirty_fingerprint=" + myDirtyFingerprint` to the existing eval-time `mesonFlags` list - - Observable: `nix build .#xcecore_debug` (no `--impure`, the documented canonical command) still succeeds; editing a tracked file in the flake's source and re-evaluating changes the resulting binary's `--version` fingerprint +- [x] 2.2 (P) Nix flake computes the fingerprint via self.narHash at eval time, independently of myCommitId + - `myDirtyFingerprint` computed as `builtins.hashString "sha256" self.narHash` truncated to 8 hex chars (`self.narHash` is Nix's SRI base64 format, not hex -- rehashing was confirmed necessary, not optional), gated on the same `hasAttr "shortRev"`-style clean/dirty branch `myCommitId` already uses; threaded through `nixSupport/default.nix` into `xce.nix`/`xcecli.nix`'s eval-time `mesonFlags` list as `-Ddirty_fingerprint=`, alongside the untouched `-Dcommit_id=`; `build_time`'s separate `preConfigure`/`mesonFlagsArray` mechanism left completely undisturbed + - Observable confirmed live (independently re-verified by review, including a full re-run of both `nix build ./src#xcecore_debug` and `./src#xcecli`, no `--impure`): `nix eval` resolves a real, non-empty, content-sensitive `-Ddirty_fingerprint=` value matching an independent Python cross-check; editing a tracked file changes the resolved value; `-Dcommit_id=` unaffected throughout; `nix log` confirms both values reach the real `mesonConfigurePhase` invocation. Full artifact completion is blocked in this sandbox by a pre-existing, unrelated network limitation (an autoscaler Go module fetch needing `goproxy.cn`, confirmed recurring identically on two disjoint Go module sets) -- not a regression from this task, and orthogonal to the eval/meson-configuration behavior this task's Observable actually requires - _Requirements: 1.1, 1.2, 1.3, 5.1_ - _Boundary: Nix packaging_ diff --git a/src/flake.nix b/src/flake.nix index 836d0cb7..1845934d 100644 --- a/src/flake.nix +++ b/src/flake.nix @@ -29,6 +29,33 @@ # `preConfigure`, via stdenv's bundled `date -u`) -- see those files. # Nothing build-time-related needs to be threaded through the flake # or nixSupport/default.nix any more; only myCommitId still does. + # dirty-content-fingerprint task 2.2: myDirtyFingerprint is a new, + # independent eval-time value alongside myCommitId (myCommitId's own + # computation above, including its self.dirtyShortRev-derived -dirty + # suffix, is NOT touched by this addition). Unlike build_time + # (builtins.currentTime, stripped under pure-eval -- see the note + # above), self.narHash is a genuine flake self-introspection field + # with no pure-eval trap: it is reachable and content-sensitive with + # no --impure needed (empirically re-confirmed live: editing a + # tracked file changes it, reverting reproduces the original value + # exactly). self.narHash's own string form is SRI-style base64 + # ("sha256-="), not hex, so it is rehashed via + # builtins.hashString "sha256" (which DOES return a base16/hex + # digest) and truncated to 8 hex chars to match the dev-loop path's + # format (xce-build.sh task 2.1's `sha256sum | cut -c1-8`). Resolves + # to empty on a clean tree, mirroring myCommitId's own + # hasAttr "shortRev" branch immediately above (a clean tree only has + # self.shortRev; a dirty tree also has self.dirtyShortRev). + # KNOWN, ACCEPTED LIMITATION (see research.md "Nix path accepts the + # untracked-file blind spot"): self.narHash does not reflect + # brand-new untracked files unless they are git-add-staged first -- + # deliberately not engineered around here (would require adding git + # as a nix sandbox dependency, an explicit prior architectural + # decision this feature does not revisit). + myDirtyFingerprint = + if builtins.hasAttr "shortRev" self + then "" + else builtins.substring 0 8 (builtins.hashString "sha256" self.narHash); in flake-utils.lib.eachSystem supportedSystems ( system: let @@ -38,7 +65,7 @@ ]; pkgs = import nixpkgs { inherit overlays system;}; xpkgs = import ./nixSupport/default.nix { - inherit pkgs myCommitId; + inherit pkgs myCommitId myDirtyFingerprint; }; appImageScriptContent = builtins.readFile ./tools/appimage/entry.sh; appImageEntryPackage = pkgs.writeShellApplication { diff --git a/src/nixSupport/default.nix b/src/nixSupport/default.nix index d4a65bdf..4eb52e31 100644 --- a/src/nixSupport/default.nix +++ b/src/nixSupport/default.nix @@ -1,6 +1,7 @@ { pkgs, myCommitId, + myDirtyFingerprint, ... }: let @@ -10,18 +11,21 @@ let # builtins.currentTime is unavailable under flake pure-eval. Only # myCommitId (a genuine eval-time/flake-level value) still needs to be # passed down. + # dirty-content-fingerprint task 2.2: myDirtyFingerprint is forwarded the + # same way myCommitId already is -- a second, independent eval-time value, + # not a build-phase one. xcecore = pkgs.callPackage ./xce.nix { - inherit pkgs myCommitId; + inherit pkgs myCommitId myDirtyFingerprint; withDebug = false; withAsan = false; }; xcecore_debug = pkgs.callPackage ./xce.nix { - inherit pkgs myCommitId; + inherit pkgs myCommitId myDirtyFingerprint; withDebug = true; withAsan = true; }; xcecli = pkgs.callPackage ./xcecli.nix { - inherit pkgs myCommitId; + inherit pkgs myCommitId myDirtyFingerprint; }; in { diff --git a/src/nixSupport/xce.nix b/src/nixSupport/xce.nix index c2d87749..92448020 100644 --- a/src/nixSupport/xce.nix +++ b/src/nixSupport/xce.nix @@ -3,6 +3,7 @@ stdenv, pkgs, myCommitId, + myDirtyFingerprint, withDebug? false, withAsan? false, ... @@ -128,6 +129,10 @@ stdenv.mkDerivation { (lib.optional withDebug "--buildtype=debug") (lib.optional withAsan "-Db_sanitize=address") ("-Dcommit_id=" + myCommitId) + # dirty-content-fingerprint task 2.2: eval-time, plain mesonFlags entry + # -- mirrors -Dcommit_id= above, NOT the preConfigure/mesonFlagsArray + # build-phase mechanism reserved for build_time (see preConfigure above). + ("-Ddirty_fingerprint=" + myDirtyFingerprint) "-Dcomponent=xcecore" ]; } diff --git a/src/nixSupport/xcecli.nix b/src/nixSupport/xcecli.nix index 0e7256b3..b5fec062 100644 --- a/src/nixSupport/xcecli.nix +++ b/src/nixSupport/xcecli.nix @@ -3,6 +3,7 @@ stdenv, pkgs, myCommitId, + myDirtyFingerprint, ... }: @@ -39,5 +40,9 @@ stdenv.mkDerivation { mesonFlags = [ "-Dcomponent=xcecli" ("-Dcommit_id=" + myCommitId) + # dirty-content-fingerprint task 2.2: eval-time, plain mesonFlags entry + # -- mirrors -Dcommit_id= above, NOT the preConfigure/mesonFlagsArray + # build-phase mechanism reserved for build_time (see preConfigure above). + ("-Ddirty_fingerprint=" + myDirtyFingerprint) ]; }