Uh oh!
There was an error while loading. Please reload this page.
fix(2205): assert the manifest covers what install-k8s.sh actually sources - #770
Conversation
…urces gen-manifest.sh already cross-checked TWO declarations against each other -- its own FILES against install.sh's, and WINDOWS_FILES against install.ps1's. Both can agree and both be wrong, because neither is ever compared to what the installer actually sources. So a new scripts/lib/foo.sh that install-k8s.sh sources but that reaches neither array is NEITHER fetched by the bootstrap NOR covered by the manifest. At install time it is absent (the installer breaks on a customer machine, after CI was green) or fetched by another path and executed UNVERIFIED -- a hole in the exact property R8 exists to provide. Found by @saadqbal reviewing client#755. The sets agree today (17 = 17), so this arms a green check rather than importing a backlog. What was missing is anything that keeps them agreeing. DERIVED, not listed -- a fourth array would be one more thing to drift. Only `${LIB_DIR}/…` sources count as repo libs: a naive `grep source` also matches `. /etc/os-release` (gpu-amd.sh, gpu-nvidia.sh) and `source "$cred_file"` (provision.sh), neither of which is in this repo. Non-transitive, and the assumption that makes that valid is a machine check rather than a comment: no lib sources another lib today, so walking install-k8s.sh alone is complete. _check_no_lib_sources_lib fails if that stops holding, because otherwise the derivation would go quietly partial -- which is the same "claim that should be a check" this family of guards exists for. MY OWN GUARD WAS UNREACHABLE AT FIRST, and mutating for the message rather than the exit code is what exposed it. Under `set -euo pipefail` a no-match grep (exit 1) aborted the script before the emptiness check could report: the run still failed, but silently and by accident, and an unreachable guard reads as coverage without being it. Now `|| rc=$?`, with grep's 1 (matched nothing) routed to the guard and >1 (unreadable file) kept as its own error -- "did not check" is never "nothing is sourced". Mutation-proved, each asserted to fail for its OWN reason and not merely to fail (backend#1729 rule 10): a lib sourced but in neither array -> "SOURCED BUT NOT COVERED" a lib sources another lib -> "deriving from install-k8s.sh alone is no longer complete" the derivation matches nothing -> "ZERO sourced libs" unmutated -> green Rides the already-required `Source-of-truth drift` via `make drift` (6/6 green), so it gates on develop and main with no new context and no branch-protection change. shellcheck clean. Fixestracebloc/backend#2205 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka
commented
Aug 20, 2026
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
saadqbal
left a comment
There was a problem hiding this comment.
The gap is real and the derivation is the right answer to it — two declarations checked against each other can both be wrong, and neither was ever compared to what install-k8s.sh actually sources. Deriving instead of adding a fourth array is right, and I like that you checked . /etc/os-release and source "$cred_file"before writing the ${LIB_DIR}/… pattern rather than after. Guard is green on the tree (17 = 17).
Both Bugbot Lows are real, and I reproduced them.
1. The rc -gt 1 branch cannot fire. The comment above it draws the distinction carefully — "1 is matched nothing; >1 is operational and is its own error, never 'no libs are sourced'" — and the pipeline defeats it:
$ rc=0; sourced="$(grep … /nonexistent.sh | grep … | tr … | sed … | sort -u)" || rc=$?
rc=1 sourced=[]
-> would report: ZERO derived / derivation broken <- MISROUTED
Under pipefail the status is the rightmost non-zero, and the second grep exits 1 on empty stdin, masking the first's 2. So an unreadable installer reports "the derivation is broken", which sends someone to debug the pattern instead of the missing file.
Cleanest fix is to stop asking a pipeline for it: read the file once, then match the variable —
src="$(cat scripts/install-k8s.sh)"|| { echo"[ERROR] could not read …">&2;exit 1; }
sourced="$(grep -oE '…'<<<"$src"| …)"— which separates "cannot read" from "no match" structurally rather than by exit-code archaeology.
2. The transitivity check fails open, and it's load-bearing.|| true swallows every failure:
$ offenders="$(grep -lE '…' scripts/lib/*.sh 2>/dev/null || true)"
offenders EMPTY -> check PASSES on an unreadable glob <- FAIL-OPEN
Low severity undersells this one, because this check is the assumption that makes the non-transitive derivation valid. If it passes on a failed read, the derivation can go quietly partial — which is the same "claim that should be a machine check" the function's own comment invokes. Worth distinguishing 1 from >1 here the way the other check tries to.
3. And the thing that would have caught both: there are no tests. This PR changes exactly one file, scripts/gen-manifest.sh. scripts/tests/gen-manifest.bats has 12 cases and none of them mentions the sourced-libs check, LIB_DIR, or transitivity — and the suite wasn't touched.
That's the part I'd push back on hardest, because the comment at :149-150 explicitly invokes the rule: "mutating the pattern and asserting WHICH message came out, not just that the exit was non-zero (backend#1729 rule 10)". That describes the practice; the PR doesn't implement it. Both findings above are precisely what asserting-which-message would have surfaced.
Cases I'd want, each asserting the specific message:
- a lib sourced by
install-k8s.shbut absent fromFILES→ the SOURCED BUT NOT COVERED arm - a lib in
FILESthat nothing sources → the over-fetched arm - the pattern mutated so it matches nothing → "derived ZERO", not a pass
install-k8s.shunreadable → "could not read", not "derivation is broken" (fails today)- a lib that sources another lib → the transitivity refusal
- the lib glob unreadable → a refusal, not a pass (fails today)
The last two are the ones that turn this from a check that happens to be right today into one that stays right. Requesting changes on that basis — the idea and the derivation are good, and Prereqs — almalinux:8 is still pending anyway.
…d open, and neither had a test (Asad, #770) All three findings real; both Bugbot Lows reproduced before fixing. 1. THE `rc -gt 1` BRANCH WAS UNREACHABLE. Under `pipefail` the status is the RIGHTMOST non-zero, and the second `grep` exits 1 on empty stdin, masking the first's 2. So an unreadable installer reported "the derivation is broken" and sent the reader to debug the pattern instead of the missing file. My comment claimed that distinction worked -- a comment asserting a property the code lacks, which is the defect this guard family exists to remove. Fixed structurally rather than by exit-code archaeology, as suggested: read the file once into a variable, then match the variable. "Cannot read" and "no match" are now different statements. `|| true` is safe on the new pipeline and was not on the old one -- the input is a shell variable, so grep's 1 for "matched nothing" is the only non-zero it can produce, and that is what the ZERO guard is for. 2. THE TRANSITIVITY GUARD FAILED OPEN. `|| true` swallowed everything, so it passed on an unreadable glob -- and it is the assumption that makes the non-transitive derivation valid, so failing open there lets the derivation go quietly partial. Now 1 (no match) is the clean answer and >1 is its own refusal, with grep's stderr in the message. 3. NO TESTS, on a PR whose own comment invoked rule 10. That was the fair hit: the comment described asserting WHICH message came out while the PR asserted nothing at all. scripts/tests/gen-manifest.bats gains all six requested cases plus a seventh, each asserting its specific message: a lib sourced but absent from FILES -> "SOURCED BUT NOT COVERED" a lib in FILES that nothing sources -> "a different set of libs" the pattern matches nothing -> "ZERO sourced libs" install-k8s.sh unreadable -> "could not read" [1] a lib that sources another lib -> "no longer complete" the lib glob unreadable -> "Refusing to assume none" [2] a COMMENTED-OUT source -> "a different set of libs" [3] Verified against the PRE-FIX script with the new suite: [1] and [2] FAIL, exactly as predicted, and pass after. 19/19 now. [3] is a defect the tests found in the derivation itself. `grep -oE` ignores what precedes the match, so `#source "${LIB_DIR}/x.sh"` counted as SOURCED. Two consequences: a commented-out source could never surface as over-fetched, and my first version of the over-fetched case commented a line out and therefore mutated nothing -- it passed on the manifest-digest check, a different code path than its name claims. The select is now line-anchored (`source`/`.` must be the line's first token, which also stops a mention inside a string or heredoc counting), the over-fetched case DELETES the line instead, and [3] is its regression test. make drift 6/6 green. shellcheck clean. Refs tracebloc/backend#2205 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka
commented
Aug 20, 2026
All three addressed. You were right on every count, and finding 3 was the fair hit — the comment invoked rule 10 while the PR asserted nothing at all. 1. The |
| Case | Message |
|---|---|
a lib sourced but absent from FILES | SOURCED BUT NOT COVERED |
a lib in FILES that nothing sources | a different set of libs |
| the pattern matches nothing | ZERO sourced libs |
install-k8s.sh unreadable | could not read |
| a lib that sources another lib | no longer complete |
| the lib glob unreadable | Refusing to assume none |
| a COMMENTED-OUT source | a different set of libs |
Verified against the pre-fix script with the new suite, which is the part that matters:
not ok 16 an unreadable install-k8s.sh says 'could not read', not 'derivation is broken'
not ok 18 an unreadable lib glob is refused, not assumed clean
Both fail before, pass after — exactly the two you predicted. 19/19 now.
The seventh case is a defect the tests found in the derivation
grep -oE ignores what precedes the match, so #source "${LIB_DIR}/x.sh" counted as sourced. Two consequences: a commented-out source could never surface as over-fetched, and my first version of the over-fetched case commented a line out and therefore mutated nothing — it passed on the manifest-digest check, a different code path than its name claims. Precisely the trap you caught in _brand_rgbs on #762, one PR later.
The select is now line-anchored (source/. must be the line's first token, which also stops a mention inside a string or heredoc counting), the over-fetched case DELETES the line instead, and case 19 is the regression test for the anchoring itself.
make drift 6/6, shellcheck clean.
LukasWodka
commented
Aug 20, 2026
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…rced one (Bugbot)
Real, and it is the MIRROR of the defect fixed one commit ago. I repaired the
`sourced` pipeline and left its twin alone:
declared="$(printf '%s\n' "${FILES[@]}" | grep '^scripts/lib/' | sort -u)"
A no-match grep exits 1, so under `set -euo pipefail` a FILES array with no
scripts/lib/ entries killed the script BEFORE the sourced-vs-declared diff could
print. Fail-closed by accident, with no message naming the integrity surface just
lost -- which is precisely the shape this suite already refuses for an entirely
empty FILES (case 10).
`|| true` is safe here for the same reason it is safe on the sourced arm: the
input is a shell array, so grep's 1 for "matched nothing" is the only non-zero
possible. An empty `declared` against a non-empty `sourced` is then exactly what
the diff reports -- all 17 libs as SOURCED BUT NOT COVERED.
Case 20 isolates it deliberately. Stripping the lib entries from gen-manifest's
FILES alone would trip _check_bootstrap_in_sync FIRST, so the case would pass on
"FILES arrays differ" -- a different code path than its name claims. It strips
them from install.sh too, so the cross-check still agrees and this check is the
one that speaks.
Mutation-proved: remove the `|| true` and case 20 fails; restore and it passes.
20/20. make drift 6/6. shellcheck clean.
Refs tracebloc/backend#2205
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>LukasWodka
commented
Aug 20, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cdf8bd0. Configure here.
saadqbal
left a comment
There was a problem hiding this comment.
All three of my asks are closed, and the tests are the full list I named plus two I didn't think of. Suite 12 → 20 cases.
1. The rc split is structural now, which is better than the exit-code fix I suggested:
src="$(cat scripts/install-k8s.sh)"|| {
echo"[ERROR] could not read scripts/install-k8s.sh to derive its sourced libs.">&2echo" That is 'did not check', never 'nothing is sourced'.">&2Reading once and matching the variable means "cannot read" and "no match" can't be confused by a pipeline's rightmost status. Mutation-verified — removing that guard so an unreadable file falls through reddens exactly one case:
not ok 16 an unreadable install-k8s.sh says 'could not read', not 'derivation is broken'
2. The transitivity guard captures rc instead of swallowing it. Reverting it to || true reddens exactly an unreadable lib glob is refused, not assumed clean — the test for the bug, catching the bug.
3. And the part I'd single out: the comment at :160 distinguishing where || trueis still safe — "the input is a shell variable, so the only non-zero this can produce is grep's 1 for 'matched nothing' … No I/O can fail." That's the right response to a finding about a dangerous idiom: work out where it's actually dangerous rather than banning it everywhere. Blanket-removing it would have been easier and worse.
The two cases beyond my list — a commented-out source not counting, and a FILES array with no scripts/lib entries reporting rather than dying silently — are both the kind of thing that only shows up if you actually think about the input space.
One process note on my own work, since I've been holding others to it: my first attempt at mutation A was malformed shell, so every case failed and it told me nothing. I only caught it because bash -n and the failure pattern didn't match the property. That's the same trap the mutation notes in release-train warn about — an anchor that doesn't apply reports a confident false result — and it's worth saying out loud that it catches reviewers too.
Prereqs — almalinux:8 is the one check still in flight, so I can't lift the block yet. Nothing outstanding from me; I'll approve next pass once it lands.
saadqbal
left a comment
There was a problem hiding this comment.
Everything from me is closed — recorded on my previous comment, all three asks verified with clean mutations, suite 12 → 20 cases.
The one red check is an acknowledged registry flake, not this PR.Prereqs — opensuse/leap:15.6 failed on the image pull, and the step says so in its own words:
::error::could not pull $DISTRO from Docker Hub in 3 bounded attempts
- runner-to-registry connectivity, not this PR. Re-run this job.
followed by The operation was canceled. Nothing in this PR touches the prereq path — it's gen-manifest.sh and its bats — so a re-run of that one job should clear it. Good that the workflow diagnoses this itself rather than leaving a bare non-zero for someone to attribute.
I'm not approving over a red check even a self-identified flake, because the merge tooling reads the check state and an approval next to red says something untrue about readiness. Re-run it and I'll approve on my next pass — there's nothing else outstanding.
LukasWodka
commented
Aug 20, 2026
Thanks — and noted on the mutation-A point; that trap is why I've started asserting the anchor applied before running, since an inert mutation and real coverage are indistinguishable in a log. On the red check: That job normally takes 1.4–2.1 min — surveyed the last ~40 runs of Nothing in this PR can reach it: the diff is Re-ran just the failed job. Also worth flagging separately from this PR: |
LukasWodka
commented
Aug 20, 2026
The re-run is green — 33 pass, 0 fail — so the block is liftable whenever you next pass. One correction, because the mechanism matters for the follow-up. It was not the image pull. On attempt 1, job The So it was a Why the distinction is worth a paragraph: "registry flake" points the fix at more pull retries, and the pull is already bounded and retried and already worked. The real fact is that step 4 ran ~10x its normal duration — I surveyed the last ~40 runs of Your conclusion still holds — nothing in this PR touches the prereq path ( |
saadqbal
left a comment
There was a problem hiding this comment.
Approving. The Prereqs — opensuse/leap:15.6 flake has cleared — 33 passing, 3 skipped, nothing failing or pending, no open threads — and the head is still cdf8bd0f, the commit I verified, so nothing to re-check.
Recapping what closed, for the record on the PR rather than only in chat:
- The rc split is structural, which is better than the exit-code fix I'd suggested:
src="$(cat scripts/install-k8s.sh)" || { … }means "cannot read" and "no match" can't be conflated by a pipeline's rightmost status. Mutation-verified — removing that guard reddens exactlyan unreadable install-k8s.sh says 'could not read', not 'derivation is broken'. - The transitivity guard captures rc instead of swallowing it. Reverting to
|| truereddens exactlyan unreadable lib glob is refused, not assumed clean. - Twenty cases, up from twelve — the six I named plus a commented-out
sourcenot counting and aFILESarray with noscripts/libentries reporting rather than dying silently.
The bit I'd keep is the comment at :160 working out where || trueis still safe — "the input is a shell variable, so the only non-zero this can produce is grep's 1 for 'matched nothing' … No I/O can fail." Responding to a finding about a dangerous idiom by establishing where it's actually dangerous, rather than banning it everywhere, is the harder and better answer.
The underlying gap was worth closing too: two declarations checked against each other, neither compared to what the installer actually sources, on a signed bootstrap where a missed lib is either absent at install time or fetched and executed unverified.
Uh oh!
There was an error while loading. Please reload this page.
The gap
gen-manifest.shalready cross-checks two declarations against each other — its ownFILESagainstinstall.sh's, andWINDOWS_FILESagainstinstall.ps1's. Both can agree and both be wrong, because neither is ever compared to what the installer actually sources.So a new
scripts/lib/foo.shthatinstall-k8s.shsources but that reaches neither array is neither fetched by the bootstrap nor covered by the manifest. At install time it is either absent — the installer breaks on a customer machine, after CI was green — or fetched by some other path and executed unverified. That is a hole in the exact property R8 exists to provide.Found by @saadqbal reviewing #755, filed as tracebloc/backend#2205.
Arms green
The sets agree today, 17 = 17, in both directions. Nothing was broken; what was missing is anything that keeps them agreeing.
Derived, not listed
A fourth array would be one more thing to drift. The check derives the sourced set from the installer.
Only
${LIB_DIR}/…sources count as repo libs — a naivegrep sourcealso matches. /etc/os-release(gpu-amd.sh,gpu-nvidia.sh) andsource "$cred_file"(provision.sh), neither of which is in this repo. I checked those before writing the pattern rather than after.It is non-transitive, and the assumption that makes that valid is itself a machine check: no lib sources another lib today, so walking
install-k8s.shalone sees everything._check_no_lib_sources_libfails if that ever stops holding, because otherwise this derivation would go quietly partial — the same claim that should be a check this whole family of guards exists for.My own guard was unreachable at first
Worth recording, because it is this epic's defect in miniature and only the right kind of mutation caught it.
Under
set -euo pipefail, a no-matchgrep(exit 1) aborted the script before the emptiness check could report. The run still failed — but silently, and by accident. An unreachable guard reads as coverage without being it.I found it by asserting which message came out, not merely that the exit was non-zero. Checking only the exit code showed "fail-closed ✓" and would have shipped a dead guard. Now
|| rc=$?, with grep's1(matched nothing) routed to the guard and>1(unreadable file) kept as its own error — did not check is never nothing is sourced.Mutation proof
Each asserted to fail for its own reason, not just to fail (workspace canon rule 10 — a bare "it went red" can be a different code path than the test's name claims):
SOURCED BUT NOT COVEREDderiving from install-k8s.sh alone is no longer completeZERO sourced libsscripts/manifest.sha256 is up to date.Each mutation asserted its anchor applied before running — an inert mutation and real coverage look identical in a log.
Where it gates
Inside
gen-manifest.sh --check, so it rides the already-requiredSource-of-truth driftviamake drift(6/6 green). No new required context, no branch-protection change — the pattern established in #755.Test plan
make drift6/6 green;gen-manifest.sh --checkgreenbash -n+shellcheck --severity=errorcleanFixes tracebloc/backend#2205
🤖 Generated with Claude Code
Note
Cursor Bugbot is generating a summary for commit 242ad26. Configure here.