myxce/.kiro/specs/dirty-content-fingerprint/requirements.md
Rongsong Shen d3c583b339
docs(dirty-content-fingerprint): add spec (requirements, design, tasks)
Extends the already-shipped build-time-versioning feature so --version
output can distinguish different uncommitted-change states via a short
content-based fingerprint, instead of just a boolean -dirty flag.

Empirically confirmed during design (live probe against src/flake.nix,
reverted) that Nix's self.narHash is reachable at eval-time and content-
sensitive, resolving the spec's one open technical question for the
production build path without requiring git inside the sandbox.

Caught and corrected a design flaw before finalizing: an earlier approach
would have retired commit_id's -dirty suffix at its computation site,
which traces through to silently break the startup log's existing
modified-tree signal (same underlying value). Resolved by leaving
commit_id completely untouched everywhere and doing the -dirty-stripping
only inside --version's composition, only when a fingerprint is available
-- which also satisfies Requirement 5.2's fallback for free.

Independent task-graph sanity review (kiro-spec-tasks Step 3.5) returned
NEEDS_FIXES on 3 minor items; all fixed and self-verified before approval.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 03:45:02 +00:00

9.1 KiB

Requirements Document

Project Description (Input)

Dirty-content fingerprint for --version output: extends the build-time-versioning feature (already shipped) so the version string can distinguish between different uncommitted-change states at build time, not just a boolean "-dirty" flag. Today, xce-build.sh computes commit_id as git rev-parse --short HEAD with a plain "-dirty" suffix when the tree has uncommitted changes -- this means two different builds made from the same dirty commit (edit file, rebuild, edit further, rebuild again) are indistinguishable from their version string alone, since the suffix never changes as long as some uncommitted change exists.

New behavior: when the source tree is clean, --version continues to show just <commit_id> (no change). When the tree is dirty, --version shows <commit_id>-<hash> where <hash> is a short (e.g. 8 hex char) content-based digest computed from exactly what git add -A && git commit would capture -- i.e. git diff HEAD's content for modified/staged tracked files, plus the full content of untracked files. Any actual content change (even to the same set of files) must change the hash; an unchanged uncommitted state must produce the same hash across repeated builds. This is a stricter version of the same requirement the original feature already implements ("mark version info as modified when uncommitted changes exist"), not a new requirement.

Superseded during design (kept here for input-history fidelity, not as authoritative intent -- see research.md's "Leave commit_id's computation fully untouched" decision for the resolved approach): an earlier framing of this input proposed that "the literal '-dirty' string is retired in favor of the hash's mere presence." That approach was rejected after design tracing revealed it would silently break the startup log's existing modified-tree signal (the startup log reads the same commit_id value --version does). The resolved design instead leaves commit_id's -dirty suffix completely untouched everywhere, and strips it only inside the --version-composition call, only when a fingerprint is actually available.

Scope: --version output only, for all six components (controller, scheduler, worker, apiserver, xcecli, xceadmin) -- the existing startup log line (controller/scheduler/worker/apiserver) is explicitly OUT of scope for this enhancement and keeps its current behavior/format unchanged; this is a deliberate, scoped exception to the original feature's guarantee that --version and the startup log never disagree, analogous to how xcecli's startup log was already excluded in the original spec for being a one-shot CLI.

Open technical question requiring investigation during design (light discovery, extension feature): the project's dev-loop build path (xce-build.sh) has real git available and can straightforwardly compute this hash via git diff HEAD + untracked file contents. The production Nix build path (xce.nix/xcecli.nix) runs in a sandboxed derivation without git on PATH by design (this repo deliberately keeps git out of the nix devShell buildInputs) and currently gets its "-dirty" marker for free from Nix's own eval-time self.dirtyShortRev introspection, which is NOT a content hash (it can't distinguish two different dirty states either). Design must investigate whether Nix's flake evaluator exposes an equivalent eval-time content hash (e.g. self.narHash) that could serve this purpose without shelling out to git inside the sandbox -- verify this empirically rather than assuming. If no such eval-time-safe content hash exists for the sandboxed path, document that as a real constraint and decide a reasonable fallback -- do not invent a mechanism that would require adding git as a nix sandbox dependency, since that was an explicit prior architectural decision in the original spec's research.

Hash algorithm/length is an implementation detail for design to decide (short, stable, no external dependency beyond what's already available in each toolchain/environment).

Boundary Context (Optional)

  • In scope: computing and displaying, in --version output only, a content-derived indicator that distinguishes different uncommitted-change states, for controller, scheduler, worker, apiserver, xcecli, and xceadmin; graceful degradation when that indicator cannot be determined in a given build environment.
  • Out of scope: any change to the startup log format of controller, scheduler, worker, or apiserver (unchanged by this feature); the underlying commit identifier / build timestamp mechanism already shipped by the build-time-versioning feature (extended, not replaced); exposing the names of changed files or diff content anywhere in --version output (this feature only makes two builds comparable for content-equality, it does not reveal what changed); a tool for comparing or explaining the difference between two builds; retrofitting this indicator into already-built/deployed binaries.
  • Adjacent expectations: this feature depends on the already-shipped build-time-versioning feature's commit identifier and build timestamp display; it is additive to that mechanism. An operator wanting to know exactly what changed between two builds is still expected to inspect the source tree themselves (e.g. via git diff); this feature only tells them whether two builds' uncommitted content was identical or different, it does not show the content itself.

Requirements

Requirement 1: Content-Based Distinguishability of Uncommitted State

Objective: As an operator comparing two builds made from the same base commit at different points during active development, I want their version identifiers to differ whenever the uncommitted content differs, so that I can tell apart two builds that a plain "modified" marker would otherwise show as identical.

Acceptance Criteria

  1. When the source tree contains uncommitted changes at build time, the system shall include, alongside the build's commit identifier, a content-derived indicator reflecting the actual content of those uncommitted changes.
  2. If two builds are produced from the same commit but with different uncommitted content, then the system shall produce different content-derived indicators for the two builds.
  3. If two builds are produced from the same commit with identical uncommitted content, then the system shall produce the identical content-derived indicator for both builds.
  4. The system shall derive the content-derived indicator only from files that would be included by committing all current changes (tracked-file modifications and untracked files), excluding any file the source tree's ignore rules exclude from commits.

Requirement 2: Clean-Tree Behavior Unchanged

Objective: As an operator inspecting a build produced from an exact, unmodified commit, I want its version identifier to remain exactly the commit identifier with no additional marker, so a clean build stays as concise as it is today and is never confused with a modified one.

Acceptance Criteria

  1. While the source tree contains no uncommitted changes at build time, the system shall present the version identifier as the commit identifier alone, with no content-derived indicator or other modified-state marker appended.

Requirement 3: Scope — Version Query Surface Only

Objective: As an operator, I want the content-derived indicator available when I explicitly query a component's version, without changing the routine startup log output I already rely on.

Acceptance Criteria

  1. When an operator queries a component's version via its version-query command, the system shall include the content-derived indicator described in Requirement 1 for that build.
  2. The system shall present the commit identifier and build timestamp in the startup log of controller, scheduler, worker, and apiserver without the content-derived indicator, leaving the existing startup log format unchanged.

Requirement 4: Applies to Every Version-Displaying Component

Objective: As an operator, I want this distinguishing capability available consistently across every component that already displays version information, so I do not need to remember which components support it.

Acceptance Criteria

  1. The system shall provide the content-derived indicator described in Requirement 1 for controller, scheduler, worker, apiserver, xcecli, and xceadmin.

Requirement 5: Graceful Degradation When Content Cannot Be Determined

Objective: As a developer running the build tooling in an environment where the uncommitted content cannot be reliably inspected, I want the build to still succeed and clearly indicate the limitation, so that I never see a fabricated or misleading indicator.

Acceptance Criteria

  1. If the build tooling cannot determine the content of uncommitted changes at build time, then the system shall omit the content-derived indicator rather than fabricate one.
  2. If the build tooling cannot determine the content of uncommitted changes at build time while the source tree does have uncommitted changes, then the system shall still indicate that the version information reflects a modified tree, so the operator is not misled into believing the build is clean.