diff --git a/CHANGELOG.md b/CHANGELOG.md index 790e4bc3..46049bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1364,6 +1364,15 @@ true until the next version shipped. runner and the development loop. Without that the matrix, which builds once per major and then sets PGC_SKIP_BUILD, would have reported every suite as unverified. + The reader for the source field now reads its first line only. Stripping hex from + the whole file was right while a stamp was one line, and wrong as soon as there were + two: a source that cannot be fingerprinted writes an empty first line, and the + whole-file read returned the library digest as the source, turning a documented + unverified into a refusal that named a library digest as a source fingerprint. That + was introduced by the second line rather than found lying in wait, so it is fixed + here. Reported by Joshua D. Drake, who swept every hex extraction in the harness to + establish it was the only one. + The refusal on a changed source had never been exercised by anyone before this change, only read. It is now driven end to end, along with the three other states. diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index e43e615e..e45b869b 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -352,11 +352,13 @@ harness_selftest 340-the-binary-must-be-built-from adding a source file moves it harness_selftest 340-the-binary-must-be-built-from an added file appears in the manifest by name never - harness_selftest 340-the-binary-must-be-built-from an added file shows up in the report never - harness_selftest 340-the-binary-must-be-built-from an empty manifest is reported as empty, not as silence never - +harness_selftest 340-the-binary-must-be-built-from an unfingerprintable source reads as empty, not as the library digest never - harness_selftest 340-the-binary-must-be-built-from an unhashable tree has an empty manifest never - harness_selftest 340-the-binary-must-be-built-from an unreadable b.c yields no fingerprint, not a wrong one never - harness_selftest 340-the-binary-must-be-built-from an unreadable c.c yields no fingerprint, not a wrong one never - harness_selftest 340-the-binary-must-be-built-from an unreadable library is not a failure never - harness_selftest 340-the-binary-must-be-built-from and a non-numeric timestamp is unknown rather than compared as text never - +harness_selftest 340-the-binary-must-be-built-from and a short first line does not splice the second into it never - harness_selftest 340-the-binary-must-be-built-from and a stale source with a replaced binary is still refused for the source never - harness_selftest 340-the-binary-must-be-built-from and an uncomputable current fingerprint is unknown, not stale never - harness_selftest 340-the-binary-must-be-built-from and an unreadable installed library is unknown, not replaced never - @@ -404,6 +406,7 @@ harness_selftest 340-the-binary-must-be-built-from premise: the tree fingerprint harness_selftest 340-the-binary-must-be-built-from premise: the unprivileged read agrees while everything is readable never - harness_selftest 340-the-binary-must-be-built-from premise: the writer wrote a stamp at all never - harness_selftest 340-the-binary-must-be-built-from renaming a source file moves the fingerprint too never - +harness_selftest 340-the-binary-must-be-built-from so its verdict is unknown rather than a spurious stale never - harness_selftest 340-the-binary-must-be-built-from so the tree still fingerprints from its root files alone never - harness_selftest 340-the-binary-must-be-built-from so the verdict is fresh, not unknown never - harness_selftest 340-the-binary-must-be-built-from so the verdict is unknown -- UNVERIFIED -- and never stale never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index 43d884cc..bf938dac 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -34,4 +34,4 @@ suites_not_covered 250 # Without that it is a hand-maintained count that drifts, which is the failure # this repository has spent a day proving. It is not a ceiling; it is a # measurement that must be true. -checks_never_observed_red 843 +checks_never_observed_red 846 diff --git a/test/lib.sh b/test/lib.sh index af59479e..34cfadcb 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -947,8 +947,26 @@ pgc_read_installed_stamp() { # pgc_read_installed_stamp FILE -> digest or empty } pgc_read_source_stamp() { # pgc_read_source_stamp FILE -> hash or empty + # THE FIRST LINE ONLY, and this is the mirror of the trap in the reader above + # (#959 review, @jdatcmd). Stripping hex from the WHOLE FILE was right while a + # stamp was one line and became wrong the moment this change added a second: + # + # source unfingerprintable + a digest recorded + # file "" + "d312a10c0cfb" + # whole-file read d312a10c0cfb <- the LIBRARY digest, as the source + # verdict stale <- was `unknown` before this change + # decision refuse-source <- a FATAL naming a library digest + # as a source fingerprint + # + # Before, that case wrote a one-line empty stamp, read empty, and reported + # UNVERIFIED -- which is the documented behaviour for a tree that cannot be + # fingerprinted. So the regression was introduced by the second line, not found + # lying in wait, and it belongs in this change rather than a follow-up. + # + # A short first line would also have spliced across the newline: "abc" plus + # "d312a10c0cfb" read as "abcd312a10c0". [ -r "${1:-}" ] || { echo ""; return; } - tr -dc 'a-f0-9' < "$1" | head -c 12 + sed -n '1p' "$1" | tr -dc 'a-f0-9' | head -c 12 } # The stamp lives beside the tree that built the binary, keyed by major, because one diff --git a/test/selftest/340-the-binary-must-be-built-from.sh b/test/selftest/340-the-binary-must-be-built-from.sh index 1e4233b3..352d9c5b 100644 --- a/test/selftest/340-the-binary-must-be-built-from.sh +++ b/test/selftest/340-the-binary-must-be-built-from.sh @@ -115,6 +115,22 @@ check "a two-line stamp still yields the source fingerprint first" \ "$(pgc_read_source_stamp "$_bs")" "a0e6afc3e13e" check "and yields the library digest second" \ "$(pgc_read_installed_stamp "$_bs")" "d312a10c0cfb" + +# THE MIRROR OF THAT TRAP, in the reader this change did not touch (@jdatcmd swept +# for it). `pgc_read_source_stamp` stripped hex from the WHOLE file, which was right +# while a stamp was one line. With two, a source that cannot be fingerprinted writes +# an empty first line and the whole-file read returns the LIBRARY DIGEST as the +# source: verdict `stale` instead of `unknown`, so a documented UNVERIFIED became a +# FATAL naming a library digest as a source fingerprint. Introduced by the second +# line, so it is fixed here rather than left as a residual. +pgc_write_source_stamp "$_bs" "" "d312a10c0cfb" +check "an unfingerprintable source reads as empty, not as the library digest" \ + "$(pgc_read_source_stamp "$_bs")" "" +check "so its verdict is unknown rather than a spurious stale" \ + "$(pgc_freshness_verdict "$(pgc_read_source_stamp "$_bs")" a0e6afc3e13e)" "unknown" +printf 'abc\nd312a10c0cfb\n' > "$_bs" +check "and a short first line does not splice the second into it" \ + "$(pgc_read_source_stamp "$_bs")" "abc" unset _bs # The fingerprint has to MOVE when a build input moves and STAY when nothing does.