feat(dirty-content-fingerprint): add --version-only composition with dirty_fingerprint

Adds xce_compose_version_ex() as a sibling of the existing, completely
untouched xce_compose_version(): when dirty_fingerprint is non-empty, it
strips a trailing -dirty from the commit field (anchored tail-slice
strcmp, not a substring search) and appends -<dirty_fingerprint> instead;
when empty/NULL, it delegates directly to the original function, so
output is structurally guaranteed byte-identical -- including the
Requirement 5.2 fallback case (dirty tree, fingerprint computation
failed) where commit_id's own pre-existing -dirty suffix already signals
"modified" with no new sentinel needed.

No main.c call sites touched -- consuming daemons (tasks 3.2/3.3) still
call the original xce_compose_version() and will switch to the new
function themselves.

Verified independently by review, full lib suite on both debug and ASAN
profiles: 301 tests, 0 failures, 0 ignored; suffix-anchoring, buffer
safety, and the empty-fingerprint delegation path all confirmed correct
by hand-tracing the implementation, not just trusting the test results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-21 05:19:12 +00:00
parent fc5cba15e1
commit 7ebcba6d49
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 178 additions and 5 deletions

View file

@ -19,11 +19,10 @@
- _Boundary: Nix packaging_
- [ ] 3. Core: C daemon --version fingerprint display
- [ ] 3.1 Extend the shared version-composition mechanism with an optional fingerprint field, without touching commit_id
- `commit_id`'s own computation and its existing `-dirty` suffix are **not modified** -- this task only changes how the `--version` composition function *displays* it
- `xce_compose_version()` (or a sibling function reusing its internals) gains an optional `dirty_fingerprint` parameter, consumed only by the `--version` call path: when non-empty, the composition strips a trailing `-dirty` from `commit_id` (if present) and appends `-<dirty_fingerprint>` instead, producing `<commit_id>-<hash>`; when empty, the composition uses `commit_id` completely as-is (already correctly showing the bare hash on a clean tree, or `<hash>-dirty` on a dirty tree where the fingerprint computation failed -- Requirement 5.2, satisfied for free)
- The existing signature/call sites used by startup-log composition are left completely unaffected -- no new parameter reaches them, and `commit_id` is passed to them exactly as before
- Observable: new unit tests in `test_utils.c` confirm all three cases (empty field + clean `commit_id` -> `<commit_id>` alone; empty field + `-dirty`-suffixed `commit_id` -> `<commit_id>-dirty` unchanged; non-empty field -> `-dirty` stripped and replaced with `-<hash>`) and confirm the startup-log-facing composition path's existing tests still pass completely unmodified
- [x] 3.1 Extend the shared version-composition mechanism with an optional fingerprint field, without touching commit_id
- New sibling function `xce_compose_version_ex()` added alongside the completely untouched `xce_compose_version()` (zero diff lines in its body or its 3 pre-existing tests); anchors the `-dirty` strip via a true tail-slice `strcmp` (not `strstr`), so it cannot false-match a `-dirty` substring elsewhere; correctly appends `-<fingerprint>` with exactly one dash even when the commit field has no `-dirty` to strip; delegates via a genuine `return xce_compose_version(...)` call (not a re-implementation) when the fingerprint is empty/NULL, structurally guaranteeing byte-identical output, proven by a dedicated equivalence regression test
- No `main.c` call sites were touched by this task (grep-confirmed all still call the original, untouched `xce_compose_version()`) -- that's tasks 3.2/3.3's job
- Observable confirmed live (independently re-verified by review, full lib suite on both debug and ASAN profiles): 301 tests, 0 failures, 0 ignored on both profiles; all 3 required cases present with byte-exact assertions, plus 2 meaningful additional regression/defensive tests; buffer handling confirmed snprintf-bounded with no overflow risk
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 3.1, 3.2, 5.2_
- _Boundary: utils.h, utils.c_

View file

@ -170,6 +170,21 @@ extern char* verbose_to_loglevel(int);
extern const char* xce_version_field(const char*);
extern int xce_compose_version(char*, size_t, const char*, const char*,
const char*);
/* dirty-content-fingerprint task 3.1: --version-only sibling of
* xce_compose_version() above. Same composition (prefix + commit +
* " built " + build_time), plus one more optional field: when
* dirty_fingerprint is non-empty, a trailing "-dirty" on commit is
* stripped and "-<dirty_fingerprint>" is appended in its place, producing
* "<commit>-<dirty_fingerprint>". When dirty_fingerprint is NULL or empty,
* this function delegates straight to xce_compose_version() and produces
* byte-identical output -- commit_id's own computation and its existing
* -dirty suffix are never touched by this function. The startup-log
* composition call sites (controller/scheduler/worker main.c) must keep
* calling xce_compose_version() directly, never this function -- this is
* what keeps the startup log's format unchanged per Requirement 3.2 of the
* dirty-content-fingerprint spec. */
extern int xce_compose_version_ex(char*, size_t, const char*, const char*,
const char*, const char*);
extern void set_error(char*, char*, int, int);
extern ectx_t* get_ectx(void);
extern int xce_vprintf(const char*, va_list);

View file

@ -189,6 +189,95 @@ void test_compose_version_null_fields(void)
TEST_ASSERT_EQUAL_STRING("xce v0.0.1-unknown built unknown", buf);
}
/* dirty-content-fingerprint task 3.1: xce_compose_version_ex(), the
* --version-only sibling of xce_compose_version(). commit_id's own
* computation/its "-dirty" suffix are never touched anywhere -- these
* tests cover exactly the 3 cases from the task's Observable, plus a
* direct regression proof that the startup-log-facing xce_compose_version()
* is unaffected. */
/* Case: non-empty dirty_fingerprint + a "-dirty"-suffixed commit_id -> the
* "-dirty" suffix is stripped and replaced with "-<dirty_fingerprint>"
* (not "-dirty-<fingerprint>", not "-dirty<fingerprint>"). */
void test_compose_version_ex_fingerprint_strips_dirty_suffix(void)
{
char buf[128];
int n;
n = xce_compose_version_ex(buf, sizeof(buf), "xce v0.0.1-", "a1b2c3d-dirty",
"2026-08-20T10:00:00Z", "a1b2c3d4");
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_EQUAL_STRING(
"xce v0.0.1-a1b2c3d-a1b2c3d4 built 2026-08-20T10:00:00Z", buf);
}
/* Case: empty dirty_fingerprint + a clean commit_id (no "-dirty" suffix) ->
* output is the commit_id alone, byte-identical to the no-fingerprint
* composition. */
void test_compose_version_ex_empty_fingerprint_clean_commit(void)
{
char buf[128];
int n;
n = xce_compose_version_ex(buf, sizeof(buf), "xce v0.0.1-", "a1b2c3d",
"2026-08-20T10:00:00Z", "");
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_EQUAL_STRING("xce v0.0.1-a1b2c3d built 2026-08-20T10:00:00Z",
buf);
}
/* Case: empty dirty_fingerprint + a "-dirty"-suffixed commit_id -> output is
* UNCHANGED, i.e. still shows "<commit_id>-dirty" -- Requirement 5.2's
* fallback, proving this function does NOT alter this case at all. */
void test_compose_version_ex_empty_fingerprint_dirty_commit_unchanged(void)
{
char buf[128];
int n;
n = xce_compose_version_ex(buf, sizeof(buf), "xce v0.0.1-", "a1b2c3d-dirty",
"2026-08-20T10:00:00Z", "");
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_EQUAL_STRING(
"xce v0.0.1-a1b2c3d-dirty built 2026-08-20T10:00:00Z", buf);
}
/* Regression: a NULL dirty_fingerprint (as well as "") must delegate the
* same as an empty one -- defensive NULL-safety, matching this file's
* existing test_compose_version_null_fields() convention for the other
* fields. */
void test_compose_version_ex_null_fingerprint_matches_empty(void)
{
char buf[128];
xce_compose_version_ex(buf, sizeof(buf), "xce v0.0.1-", "a1b2c3d-dirty",
"2026-08-20T10:00:00Z", NULL);
TEST_ASSERT_EQUAL_STRING(
"xce v0.0.1-a1b2c3d-dirty built 2026-08-20T10:00:00Z", buf);
}
/* Regression: the startup-log-facing xce_compose_version() itself is
* unaffected by this task -- for any given inputs, calling it directly
* produces byte-identical output to calling xce_compose_version_ex() with
* an empty dirty_fingerprint (its documented delegate-through path). This
* is the concrete proof that no shared internals were changed underneath
* the already-shipped function. */
void test_compose_version_ex_matches_original_when_fingerprint_absent(void)
{
char buf_orig[128];
char buf_ex[128];
xce_compose_version(buf_orig, sizeof(buf_orig), "xce v0.0.1-",
"a1b2c3d-dirty", "2026-08-20T10:00:00Z");
xce_compose_version_ex(buf_ex, sizeof(buf_ex), "xce v0.0.1-",
"a1b2c3d-dirty", "2026-08-20T10:00:00Z", "");
TEST_ASSERT_EQUAL_STRING(buf_orig, buf_ex);
}
void test_utils(void)
{
printf("\n>> utils test\n\n");
@ -204,4 +293,9 @@ void test_utils(void)
RUN_TEST(test_compose_version_both_set);
RUN_TEST(test_compose_version_both_unset);
RUN_TEST(test_compose_version_null_fields);
RUN_TEST(test_compose_version_ex_fingerprint_strips_dirty_suffix);
RUN_TEST(test_compose_version_ex_empty_fingerprint_clean_commit);
RUN_TEST(test_compose_version_ex_empty_fingerprint_dirty_commit_unchanged);
RUN_TEST(test_compose_version_ex_null_fingerprint_matches_empty);
RUN_TEST(test_compose_version_ex_matches_original_when_fingerprint_absent);
}

View file

@ -337,6 +337,71 @@ int xce_compose_version(char* buf, size_t bufsz, const char* prefix,
xce_version_field(commit), xce_version_field(build_time));
}
/* dirty-content-fingerprint task 3.1: buffer for the commit-id portion
* after an optional "-dirty" strip + "-<dirty_fingerprint>" append, built
* by xce_compose_version_ex() below before handing it to
* xce_compose_version()'s own "%s%s built %s" formatting. Sized generously
* for a full 40-hex-char SHA-1 commit id + "-dirty" (never both present at
* once, but sized as if they were) + "-" + a generous fingerprint length. */
#define XCE_COMPOSE_COMMIT_BUF_SIZE 128
static const char XCE_DIRTY_SUFFIX[] = "-dirty";
#define XCE_DIRTY_SUFFIX_LEN (sizeof(XCE_DIRTY_SUFFIX) - 1)
/* dirty-content-fingerprint task 3.1: --version-only sibling of
* xce_compose_version() above -- see the doc comment on its extern
* declaration in utils.h for the full contract. commit_id's own
* computation and its existing "-dirty" suffix (both from the
* already-shipped build-time-versioning feature) are NOT modified by this
* function or by anything it calls -- it only changes what gets composed
* into the returned --version display string. The startup-log composition
* call sites must keep calling xce_compose_version() directly and never
* this function. */
int xce_compose_version_ex(char* buf, size_t bufsz, const char* prefix,
const char* commit, const char* build_time,
const char* dirty_fingerprint)
{
const char* commit_field;
size_t commit_len;
char commit_buf[XCE_COMPOSE_COMMIT_BUF_SIZE];
if (buf == NULL || bufsz == 0) {
return -1;
}
if (dirty_fingerprint == NULL || dirty_fingerprint[0] == '\0') {
/* No fingerprint available -- delegate straight to the untouched,
* already-shipped function. Byte-identical output, including its
* own "unknown" fallback for a NULL/empty commit, and including
* commit's own "-dirty" suffix (if any) left completely as-is --
* this is Requirement 5.2's fallback, satisfied for free. */
return xce_compose_version(buf, bufsz, prefix, commit, build_time);
}
/* dirty_fingerprint is non-empty: strip a literal trailing "-dirty"
* from the commit field (anchored to the actual suffix position, not a
* blind substring removal -- see design.md's Risks section) and append
* "-<dirty_fingerprint>" in its place. */
commit_field = xce_version_field(commit);
commit_len = strlen(commit_field);
if (commit_len >= XCE_DIRTY_SUFFIX_LEN
&& strcmp(commit_field + (commit_len - XCE_DIRTY_SUFFIX_LEN),
XCE_DIRTY_SUFFIX)
== 0) {
snprintf(commit_buf, sizeof(commit_buf), "%.*s-%s",
(int)(commit_len - XCE_DIRTY_SUFFIX_LEN), commit_field,
dirty_fingerprint);
} else {
snprintf(commit_buf, sizeof(commit_buf), "%s-%s", commit_field,
dirty_fingerprint);
}
return snprintf(buf, bufsz, "%s%s built %s",
(prefix != NULL) ? prefix : "", commit_buf,
xce_version_field(build_time));
}
static _Thread_local ectx_t ectx;
void set_error(char* file, char* func, int line, int code)