The reconcile_basis marker was never written on Windows, so all six
build_trusted_artifact_repo() tests plus artifact_export_marks_clean_basis
failed there (16 passed, 7 failed) while every Linux and macOS leg was green.
Root cause is a stamp-encoding mismatch, not shell quoting. file_hashes rows
are stamped by the semantic manifest via cbm_path_info_utf8(), which on Windows
derives mtime_ns from ftLastWriteTime -- 100-nanosecond FILETIME ticks.
db_hashes_match_disk() re-stat()ed those same files through _wstat64, whose
st_mtime carries WHOLE SECONDS, and compared the two. The values can only agree
when a file's write time lands exactly on a second boundary, so the trust check
rejected the very first row on every export, on Windows and only on Windows.
On POSIX both paths read st_mtim nanoseconds and agree, which is why the split
was clean along platform lines.
The disk side now reads through cbm_path_info_utf8 -- the same function that
wrote the rows -- so there is no encoding left to keep in sync. Its
is_regular/is_symlink guard preserves exactly the symlink refusal
reconcile_stat_no_symlink provided.
reconcile_restamp_rows deliberately keeps art_stat_mtime_ns: a restamped row is
read by the incremental classifier and by check_index_coverage, both of which
stat() and encode whole seconds on Windows. The asymmetry is now pinned with a
comment at both sites so neither is "unified" into breaking the other.
Diagnostics, which is why this took two wrong diagnoses to find:
- Export's four clean-basis preconditions were one short-circuiting && chain
collapsing to a single bool. They are now evaluated by reconcile_basis_trusted,
which records the first failing gate (head_unresolved / head_not_hex_oid /
tree_not_clean / db_hashes_differ_from_disk, the last carrying the offending
path and BOTH stamps) and logs it as artifact.reconcile_basis_omitted. The
reason is recorded rather than recomputed on demand: export's own
ensure_gitattributes leaves an untracked .gitattributes behind, so any later
evaluation answers tree_not_clean whatever the real cause was.
- build_trusted_artifact_repo() had six `return NULL` exits and reported none of
them; CI could not tell a failed `git commit` from a missing marker. Every exit
now names its step and carries the number that decided it -- git exit status,
pipeline rc, or export's recorded blocker -- surfaced through
BUILD_TRUSTED_REPO_OR_FAIL at the call site so file:line still points at the
failing test.
Verified locally on macOS by emulating the Windows encoding: truncating the
disk-side mtime to whole seconds reproduces the CI signature exactly (16 passed,
7 failed, same seven sites), and the new diagnostic names it as
`db_hashes_differ_from_disk path=a.rs reason=mtime_differs stored_mtime_ns=...
disk_mtime_ns=...`. Restored, the artifact/pipeline/incremental/mcp/index_format/
daemon_bootstrap suites are green and make lint-ci passes.
Co-authored-by: moofone <35233167+moofone@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>