Repository navigation
os/v3 puppy: the live USB logs it all and proves the boot (1bit-proof, lemond debug, a 32M ring, thermals) for c25 - #1057
Conversation
…ebug, a sized ring, thermals and a proof bundle Cycle 25 (owner: "make sure the live usb log verbose it all for proof that it works"). Five pieces, each with the measurement that decided it: 1. 1bit-proof: boot-proof.txt once per boot (cmdline, uname, Secure Boot via mokutil AND the efivar byte, modules with srcversions, amdxdna's tdr_timeout_ms, firmware hashes, PCI/accel devices, RAM/GTT/VRAM, the boot timeline, RELEASE-TAG and the sha256sums result), and a bundle of the boot's log directory + SHA256SUMS as proof-<tag>-<date>.tar.zst with its sha256 printed. A value that cannot be read is written 'n/a <reason>' and never omitted - a missing line and a zero look identical to a reviewer. 2. 10lemonade seeds log_level=debug (+ 8 MB x 3 rotation) into lemond's config.json when the key is absent and never overwrites a level somebody set. Measured on the pinned build: lemond has no level flag and no env var (only -v/--version); the accepted strings are trace|debug|info|warning|error|fatal|none, and debug is what prints 'Debug logging enabled - subprocess output will be visible' - the engine's own lines then land in lemond.log. trace is refused on purpose: per-token chatter would spend the whole evidence budget on itself. 3. 1bit-logs samples Tctl / fan rpm / package W every 10 s BUT ONLY while an engine child exists, in one loop with the 15 s log copy (two cadences, one sleeper, so they cannot drift). Unreadable sensors are 'n/a', not 0. 4. mkimage adds log_buf_len=32M to the shared cmdline and refuses to write an image without it: c23's ring wrapped and 1bit-logs could only mark the loss. 5. vm-check gains four rows: the ring size, the boot proof's labels (a file that exists but says nothing is what a stub produces), the thermal gate in both directions (engaged when serving, absent when idle), and the bundle being READABLE through zstd|tar - not merely present. Checks: bash -n, shellcheck -S warning and tools/copyright.py --check clean; 1bit-proof is exercised on this host as root (real ring, tdr_timeout_ms=15000, per-file firmware hashes, SB byte=1); the lemond seed is exercised in all three states (absent / no key / owner-set) and log_level=debug was confirmed by a live lemond run; the thermal sampler is exercised with a stubbed 'loaded' model (tctl=61 fan=n/a pkg_w=104.2) and with the real gate (no line written).
…is not in every build)
…NING summary made 1 bad file read as 2)
9819953 to
e50b1ef
Compare
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at e50b1eff — I ran the new tools rather than reading them, and one of the four new rows cannot go red (that is the one thing to fix, and it is two lines).
Ran 1bit-proof here, using its own documented overrides (ONEBIT_LOG_DATA, ONEBIT_LOGS_BIN) against a scratch data dir and a stub 1bit-logs:
1bit-proof path -> /tmp/proofdata/logs/boot-test-1
1bit-proof boot -> writes boot-proof.txt (41 lines), rc 0
1bit-proof boot (again) -> "already written this boot (--force rewrites it)", rc 0 <- written once per boot, as claimed
boot-proof.txt labels cmdline uname secureboot.mokutil secureboot.efivar lockdown module.* amdxdna.*
firmware* pci.devices memtotal_kb timeline.dmesg release sha256sums lemond.log_level
secureboot two ways "SecureBoot enabled" (mokutil) and "byte=1 (1 = enabled)" <- they agree
module.amdxdna loaded=yes srcversion=56AB94758C18FEEC838D18C vermagic=7.4.0-rc1-1bit
kernel.ring n/a dmesg is empty or restricted to root (kernel.dmesg_restrict) <- the false zero is gone
That srcversion is the same fingerprint my #1038 review used, so the proof's driver line is reading the real module and not a constant.
The SHA256SUMS fix is real. With a two-good/one-bad list: sha256sums ok=2 bad=1 rc=1. The previous "not OK" count would have said bad=2 for one bad file, exactly as the last commit claims.
The bundle works and is readable, not merely present: with ≥2 entries it prints the path, bytes 1595, sha256 dba5f47d…, and unzstd | tar -tf lists boot-test-1/boot-proof.txt and SHA256SUMS. With an empty directory it refuses, rc 1.
The other three changes check out by reading plus the run: mkimage.sh puts log_buf_len=32M in the shared cmdline and refuses to build without it in grub.cfg; 10lemonade seeds a fresh config only when the file is absent, uses setdefault so an existing log_level is never overwritten (and says so), and degrades to lemond's default when python3 is missing; 1bit-logs runs one sleeper with two cadences (10 s thermal gated on the engine child, 15 s copy) and calls 1bit-proof boot from the loop, after the /data wait. bash -n clean on all six files; shellcheck -S warning clean except the pre-existing ulimit -l (SC3045) in 10lemonade, which this PR does not touch.
The finding: "U5 boot proof" is an INFO row, so it cannot fail. Its expected value is None, and vm-check.sh prints INFO for want is None — never PASS or FAIL. A proof file that exists with none of the labels would show missing-cmdline missing-uname … in an INFO line and the run would still be green. That is the same class as the two rows this cycle set out to fix, and it is the one of the four that is supposed to catch "a stub would produce this". Two ways to fix, both small:
- add a positive sentinel to the row and expect it —
… done; echo boot-proof-okafter the label loop,"boot-proof-ok"as the expectation (the row already loops over the exact label list, so this is oneecho), or - expect a minimum, e.g.
"proof-lines=41"-style — but a count is weaker than the labels, so the sentinel is better.
Rows 1, 3 and 4 do gate (ring-sized, thermal-gate-consistent, proof-bundle-ok), so the pattern is already in the file.
Two smaller notes:
bundle()refuses when the boot directory holds fewer than 2 entries (n -lt 2) and says "refusing to bundle an empty proof" — I hit this withboot-proof.txtalone, which is not empty, and the message sends a reader looking for a missing proof rather than a missing second file. Either allow 1 (-lt 1) — the proof is the artifact — or word it as "holds 1 file(s), needs at least 2 (the proof plus at least one log)".- Your body says 12 firmware hashes on this box; I get 13 here (
firmware.amdnpu.*blobs). Not a problem — it is a live count and a blob has evidently been added — just noting the number is not stable.
Not verified: the on-image behaviour (a real boot with /data mounted, the console's Logs item offering the bundle, the thermal row on a loaded box). Everything above is the tool run against a scratch directory plus the scripts' own logic.
…ne made it INFO, so it could never fire) Measured on the c25 image: the run came back 62 PASS / 0 FAIL / 3 SKIP with this row as INFO - the very 'a sentence is not a check' shape this row exists to remove. It now requires every label to be present AND at least 20 lines, so a stub that writes only the labels cannot pass either.
…0008, not 0007' is answered by the artifact Owner: amdxdna 0008 is being built and c25 must carry it. Nothing in the gate could tell the two apart: vermagic and the MOK signature are identical for every module we have ever built, and the depmod path only says which FILE wins. The srcversion is the only field that names the source (that is what #1038 established the other way round). Measured both arms on the c25 ISO: --srcversion 56AB94758C18FEEC838D18C (what it carries) -> ok, rc=0 --srcversion DEADBEEF0000000000000000 (a stale cut) -> FAIL 'this cut must carry ... (a stale module)', rc=1 so the failure mode is a red gate, not a note.
PR Reviewer Guide 🔍(Review updated until commit ec8bb2b)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 3f2643ac — the row I flagged now genuinely gates, and I exercised all three directions rather than reading the diff.
The row is a positive sentinel now (boot-proof-complete, emitted only when no label is missing and the proof has at least 20 lines), with that as the expected value, so want is None no longer applies to it. Ran the row's own shell against a real proof and against the two ways it should refuse:
good proof (41 lines, every label) -> proof-lines=41 missing=none boot-proof-complete <- PASS
stub proof (exists, one line) -> proof-lines=1 missing= cmdline uname secureboot.efivar
module.amdxdna amdxdna.tdr_timeout_ms firmware pci.devices
memtotal_kb timeline.dmesg sha256sums (no sentinel) <- FAIL
missing file entirely -> proof-lines=0 missing=<all ten> (no sentinel) <- FAIL
restored -> boot-proof-complete <- PASS
So "a file that exists but says nothing is what a stub produces" is now a gate and not a transcript line, which was the one thing standing between this PR and my READY.
The line-count floor of 20 is a sensible second belt: my real proof is 41 lines on this box (host-invoked, dmesg restricted), and the smallest honest one would still be well above 20 even with several n/a <reason> values, since every label writes a line by design.
The two smaller notes from my first review stand and are unchanged in this head: bundle()'s n -lt 2 threshold wording ("refusing to bundle an empty proof" for a directory holding one file) and the firmware-hash count being live (13 here, 12 in the body). Neither blocks.
|
Persistent review updated to latest commit ec8bb2b |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at ec8bb2b2 (rebase-only for this PR; the review stands).
The rebase brought #1053's "U2 console engine" row in with the newer base — that is the only change in vm-check.sh:
git diff 3f2643ac…ec8bb2b2 -- os/v3/puppy/vm-check.sh | grep -c "^+.*U2 console engine" -> 1 (not this PR's)
and this PR's own row is untouched, sentinel and expectation both:
"[ -z \"$miss\" ] && [ \"${n:-0}\" -ge 20 ] && echo boot-proof-complete",
"boot-proof-complete"),
so my previous verdict holds: the row gates in all three directions (good proof → sentinel; stub proof → ten missing labels, no sentinel; missing file → proof-lines=0, no sentinel), and the 1bit-proof/10lemonade/mkimage.sh/1bit-logs behaviour I exercised by running them is unchanged in this head.
os/v3 puppy: the live USB logs it all, and the boot proves itself (
1bit-proof), for c25Owner brief
TOS3-C25-VERBOSE: "make sure the live usb log verbose it all for proof that it works." Everything is kepton
/data/logs/<boot-id>/, survives a reboot, and nothing slows inference. This PR is the image-side half of that brief;the per-request engine line (item 3) and Laya's route timing (item 5) are engine-side and are not here — see the end.
What each item became
1bit-proof boot→$LOGDIR/boot-proof.txt, written once per boot: cmdline, uname, Secure Boot two ways (mokutil --sb-stateand the efivar byte), each driver'ssrcversion+vermagic,amdxdna'stdr_timeout_ms, every NPU firmware blob's sha256,amdgpu's firmware lines, PCI ids,/dev/accel*+/dev/kfd,MemTotal/TTM/VRAM, the boot timeline, andRELEASE-TAG+ thesha256sum -c /data/SHA256SUMSresult. Called from1bit-logs' loop after it has waited for/datato mount, so the proof cannot land in RAM and vanish10lemonadeseedslog_level: "debug"(+log_max_file_size_mb: 8,log_max_files: 3) into lemond'sconfig.jsononly when the key is absent, and never overwrites a level somebody setdebuglemond printsDebug logging enabled - subprocess output will be visible, so the engine's own1bit serve:lines reachlemond.logand/data/logswith only configurationmkimage.shwriteslog_buf_len=32Minto the shared cmdline for all four boot entries and refuses to build without itlaya:lines; themsis an engine change1bit-logssamples Tctl / fan rpm / package W every 10 s only while an engine child exists, in the same loop as the 15 s log copy (two cadences, one sleeper, so they cannot drift)1bit-proof(no argument) tars this boot's directory +SHA256SUMSinto/data/proof-<tag>-<date>.tar.zst, prints the sha256 and the byte count, refuses an empty directory; offered from the console's Logs item rather than as a tenth key, because the legend is 1-9 and "the evidence" is what a person looks for under Logsvm-checkrows: the ring size, the boot proof's labels (a file that exists but says nothing is what a stub produces), the thermal gate in both directions (engaged when serving, absent when idle), and the bundle being readable (zstd -dc | tar -tf) rather than merely presentMeasurements that decided the code, not assumptions
--helphas only-v/--version;runtime_config.cppacceptstrace|debug|info|warning|error|fatal|none, defaultinfo. Proved live: a scratchconfig.jsonwithlog_level: debugstarts with
Log level: debug,Log max file size: 8 MB,Log max files: 3and printsDebug logging enabled - subprocess output will be visible.traceis refused on purpose — per-token chatter wouldspend the whole 50 MB/hour evidence budget on itself.
Telemetry: disabled, and its telemetry code is OTLP export, not local logging. Item 3's token/TTFT/tok-s line doesnot exist in lemond today — which is why it is an engine change, not a config change.
.zstblobs, not files: the first version of the proof calledsha256sumon the directory and reported "unreadable". It now hashes each blob (12 hashes on this box).Three false readings in my own first version, fixed rather than kept
0 bytes in the ring nowwhendmesgis restricted is a false zero;n/a not loadedfor a module that was loadedis a false absence (the parameter file is root-only);
unreadablefirmware was a wrong diagnosis. All three now saywhat is actually true —
n/a dmesg is empty or restricted to root,n/a no such parameter (amdxdna not loaded)vsn/a unreadable as <user>, and per-blob hashes. A missing line and a zero look identical to a reviewer, so nothing isomitted: unreadable values are written as
n/a <reason>.Two of my own harness bugs are fixed here too:
pgrep -c(not present in every build →| wc -l), andsha256sums ok=1 bad=2for one deliberately-wrong file, becausesha256sum -cprints a WARNING summary line thatgrep -vc ': OK$'counted as an entry (now counts: FAILED$).Proven, with the artifacts
1bit-proof bootas root on this boxkernel.ring 160820,secureboot.efivar byte=1,module.amdxdna srcversion=56AB94758C18FEEC838D18C,amdxdna.tdr_timeout_ms 15000, 12 firmware hashes,memtotal_kb,ttm.pages_limit,gpu.vramlog_level→ merged via python3; owner-set (trace) → untouchedtctl_c=61 fan_rpm=n/a pkg_w=104.2; with the real gate (nothing loaded): no line writtenproof-rc-2026-11-05-c25-<date>.tar.zst, 1,725 B, sha256 printed, andzstd -dc | tar -tflistsboot-proof.txt+SHA256SUMSsha256sumscountingok=1 bad=1 rc=1g3-probe --require-mokrc=0;grub.cfgcarrieslog_buf_len=32Min all four entries; the shipped1bit-proofand1bit-logsare the fixed ones (hash-checked out of the sfs)vm-checkon that image<fill in>Checks:
bash -n,shellcheck -S warningandtools/copyright.py --checkclean; the vm-check row list is compiled (78rows) rather than eyeballed.
Not in this PR, said plainly
(Laya's
ms) areapp/serve.cppchanges. They are deliberately left out of this PR because that is exactly wherenpu: a dx dispatch can hang once a context is long (614 state 8, DPU PC 0xffffffff), and the TDR then cannot restart a churned context #1051 (the NPU stall, still open with no PR) is being worked, and a conflict there would cost more than the line is
worth. At
debug, the engine's existing1bit serve:output already lands inlemond.log."20 chats" half needs a model on the stick, which the demo staging owns. The hand-off records which half was measured and
what the per-request cost is, rather than claiming the whole number.