Skip to content

os/v3/puppy: a row that runs the selftest the appliance ships, and three rows that could skip themselves - #1218

Merged
bong-water-water-bong merged 2 commits into
mainfrom
os/v3-vmcheck-selftest
Nov 6, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
os/v3-vmcheck-selftest

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

os/v3/puppy: a row that runs the selftest the appliance now ships, and three rows that could skip themselves

The gap this closes

Cycle 29's image ships /usr/local/bin/1bit-selftest (engine #1166) — the one-command proof the install sheet asks the
operator to run — and nothing in the harness ever executed it. I found that handing c29 off: the file was proved present
and byte-identical to os/mini/selftest.sh, and proved by nobody that it runs in the appliance. Same class the
U4 release-check row was added for in cycle 21 ("the c20 image reached the owner with release-check saying FAIL models
while this row printed it as INFO"), and the same class a shipped-but-dead tool reaches a board as: a line in a document.

The new row, and what it deliberately does not require

U4 selftest   present -> runs to a verdict
     PASS  -> selftest-ok
     no model staged -> selftest-skip-no-model   (SKIP, reason named)
     absent / no RESULT line -> selftest-broken-* (FAIL)

It does not demand RESULT: PASS. The image boots with an empty /data/models, where the tool's own documented answer
is RESULT: FAIL - no model to test; demanding PASS would red for correct behaviour, and a row that reds a healthy image
is a row that gets deleted. What it requires is that the tool runs to a verdict — PASS with a model, SKIP naming the
no-model reason without one, FAIL when the binary is missing or dies without a RESULT line.

The defect the row found in the harness itself (the part worth reading)

Its first version printed selftest-broken-missing — and the row came back SKIP. Reason: Puppy's console shell echoes
the typed command into the row's own output, the command contained the literal marker selftest-skip-no-model, and
vm_check_verdict.skipping() matches a *-skip-* token anywhere in the body. So the row was classified SKIP by the
echo of itself. Measured, not argued:

body handed to verdict() level token matched
the echoed command as first written (… echo $m-skip-no-model …) SKIP m-skip-no-model
the echoed command as it is now (… echo $m-$sk-no-model …) FAIL (correct: the output decides) —
selftest-broken-missing FAIL —
… no model to test … selftest-skip-no-model SKIP selftest-skip-no-model
… RESULT: PASS … selftest-ok PASS —

Two rows already in the tree carry a literal skip marker and are exposed to the same thing: U1 shipped shells (#1186 —
and it was measured self-skipping: SKIP U1 shipped shells n=0; bad=''; for… on c28, i.e. the row that exists to catch
unparseable scripts has never actually run) and U2 console heals (#1093). All three now assemble the marker from m/sk,
so the token exists only in the output, never in the command. No row's semantics change: with a model staged the new row
PASSes, and both older rows keep their own verdicts (proved in the green run below).

Proof

Shell logic, four arms, under bash and sh (dash), fixture binaries: absent → selftest-broken-missing; documented
no-model FAIL → SKIP; RESULT: PASS → PASS; dies silently → selftest-broken-no-verdict.

End to end, one vm-check each (UEFI + Secure Boot ON), on shipped artifacts:

image ships the tool? U4 selftest log
c28 0c371992… no FAIL selftest-broken-missing ~/wt/os3i/rowproof-selftest-red.log
c29 8266aff9… yes (0b4468b6…, 0755) SKIP selftest-skip-no-model (blank /data/models) ~/wt/os3i/rowproof-selftest-green.log

Row sets, stated because they differ between runs and a reader should not have to guess: #1186 (U1 shipped shells) merged
while this was being measured, so the c28 red run has one more row than the c29 run that preceded it. Red run: 81 rows,
76 PASS / 2 FAIL / 3 SKIP — the two reds are U3 models dir (c28 predates #1151, the row c29 turns green) and U4 selftest
(this change's red arm). Green run: 81 rows, 77 PASS / 0 FAIL / 4 SKIP, the four skips being the ssh key, the two board
rows, and the new selftest row with its reason.

U1 shipped shells — the row #1186 landed three hours ago — never ran once. It was measured self-skipping (SKIP U1 shipped shells n=0; bad=''; for…), i.e. the row that exists to catch a script that does not parse was itself inert. With the
marker assembled it now runs and reports shells-parse: 45 scripts under bash and sh on c28 and on c29. That is why this PR
touches a row it did not add: the class is the same one, in the file it lives in, and leaving it would mean shipping a second
inert row knowingly.

Scope

os/v3/puppy/vm-check.sh only: one new row, three marker strings assembled instead of written, and the reasons in comments.
No image change, no release-check change, no /data checker change. c29's frozen bytes (8266aff9…) ship without it.
tools/copyright.py --check rc 0, bash -n clean. No merge, nothing tagged.

…ree rows that could skip themselves

c29 ships /usr/local/bin/1bit-selftest (engine #1166) - the one-command proof the install sheet asks the
operator to run - and no row ever executed it. The file was proved present and byte-identical to
os/mini/selftest.sh, and proved by nobody that it runs in the appliance: the same class the U4 release-check
row was added for in cycle 21.

The new row does not demand 'RESULT: PASS'. The image boots with an empty /data/models, where the tool's own
documented answer is 'RESULT: FAIL - no model to test', so demanding PASS would red a healthy image - and a
row that reds a healthy image gets deleted. It requires a VERDICT: PASS with a model, SKIP naming the
no-model reason without one, FAIL when the binary is absent or dies without a RESULT line.

Its first version printed selftest-broken-missing and came back SKIP, and that is how the second half of this
commit was found: Puppy's shell echoes the typed command into the row's own output, vm_check_verdict.skipping()
matches a *-skip-* token anywhere in the body, and the command carried the marker literally - so the row was
skipped by the echo of itself. Two rows already in the tree carry a literal marker and are exposed to the same
thing. U1 shipped shells (#1186, merged tonight) was measured self-skipping, i.e. the row that exists to catch
an unparsable script had never run; U2 console heals (#1093) is latent. All three now assemble the marker from
m/sk, so the token exists only in the output. No row's semantics change.

Proved: four arms under bash and sh on fixtures (absent / no-model / pass / dies silently); the module's own
verdict mapping on the echoed command before and after (SKIP with token m-skip-no-model -> no token, so the
output decides); and end to end on two shipped artifacts, c28 0c371992 (no such file -> FAIL
selftest-broken-missing, and U1 now RUNS: 'shells-parse: 45 scripts') and c29 8266aff9 (SKIP
selftest-skip-no-model on its blank models dir, 77 PASS / 0 FAIL / 4 SKIP).

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at 6ee37ee7 — the row is right, and the harness defect it found is real: I reproduced it from two real boots' artifacts and then watched the fix clear it.

The self-skip, from the c28 run's own output (~/wt/os3i/vmcheck-rows-c28/rows.txt:16) — the row did run and did answer correctly, and it was still SKIP:

SKIP U1 shipped shells n=0; bad=''; forroot@1bit-os:/$  f in /usr/local/bin/1bit-* … echo shells-skip-no-scripts; …
  | bash: warning: command substitution: ignored null byte in input
  | shells-parse: 45 scripts under bash and sh (shells-skip-no-scripts)

feeding that body to this tree's vm_check_verdict.verdict("U1 shipped shells", "shells-parse:", body) gives SKIP, skipping() -> 'shells-skip-no-scripts' — the leaked echo, not the answer. Note why the harness's own echo-stripping missed it: the line is n=0; bad=''; for immediately followed by the serial prompt root@1bit-os:/$ , so the echo is spliced and does not contain the command's first 24 characters, which is the only thing vm-check.sh drops. So the mechanism in the comment is right, and the strip is no defence.

And the same echo on this head's text is a PASS (rowproof-selftest-green.log, c29):

PASS U1 shipped shells m=shells; sk=skip; n=0root@1bit-os:/$ ; bad=''; for … echo $m-$sk-no-scripts; …
  | shells-parse: 46 scripts under bash and sh

prompt splice included, skipping() -> None, however skipping() is what decides. That is the fix, measured on two boots rather than argued.

The new row's five arms, run here under both shells (the command lifted from vm-check.sh with the binary path parameterised, bash and dash):

absent binary           -> selftest-broken-missing            -> FAIL   (want "selftest-ok" absent)
doc'd no-model FAIL     -> RESULT: FAIL - no model to test |
                           selftest-skip-no-model             -> SKIP
RESULT: PASS            -> RESULT: PASS … | selftest-ok       -> PASS
dies with no RESULT     -> selftest-broken-no-verdict         -> FAIL
a REAL failure          -> RESULT: FAIL - the NPU route … |
                           selftest-broken-no-verdict         -> FAIL   (my extra arm)

Identical verdicts under dash. And the two real artifacts agree: FAIL U4 selftest selftest-broken-missing on c28 (no such file) and SKIP U4 selftest RESULT: FAIL - no model to test | selftest-skip-no-model on c29 — i.e. it does not red a healthy image for the tool's own documented answer, which is the thing that gets rows deleted.

Completeness: with all three markers assembled, no row command in the file still carries a literal *-skip-* token (grep -- -skip- vm-check.sh outside comments returns only the U0 verdict line, whose text is a *-skip-* marker, which cannot match the token regex — the character before -skip- is *). The other -skip- hits are comments.

Nits, neither blocking:

  • selftest-broken-no-verdict is the label the row prints when the tool did emit a verdict — a real RESULT: FAIL - <reason> lands there, and the token says the opposite. $m-failed (or $m-failed-$reason) would read true, and a person scanning the table would not have to open the log to learn the tool answered.
  • the row's 300 s is the only bound on a tool that waits with a bare wget (os/mini/selftest.sh:90-92, no -T): a wedged Lemonade or NPU turns this row into the harness's own FAIL … no answer rather than the tool's named verdict. A -T 120 in the tool, or timeout 240 inside the row, keeps the failure informative. Not introduced by this PR; it is what the new row exposes.

Not verified by me: a board run of the row with a model staged (--device npu chat inside 1bit-selftest) — that needs the board/NPU window; the VM arms above and the two c28/c29 artifacts are the evidence here.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit 2eed8eb)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incorrect SKIP logic in U4 selftest row

The U4 selftest row uses a variable-based echo ($m-$sk-no-model) to generate the skip reason, which could inadvertently match the skipping() function's pattern-matching logic if the variables are not properly sanitized or if their values contain substrings that trigger the skip condition. This could cause the row to always SKIP regardless of the actual test outcome, as observed in the initial version of this row.

("U4 selftest", "m=selftest; sk=skip; if [ ! -x /usr/local/bin/1bit-selftest ]; then echo $m-broken-missing; else "
                "/usr/local/bin/1bit-selftest > /tmp/st.log 2>&1; grep -E '^RESULT' /tmp/st.log | tail -2; "
                "v=$(sed -n 's/^RESULT: PASS.*/PASS/p;s/^RESULT: FAIL.*/FAIL/p' /tmp/st.log | head -1); "
                "if [ \"$v\" = PASS ]; then echo $m-ok; "
                "elif grep -q 'no model to test' /tmp/st.log; then echo $m-$sk-no-model; "
                "else echo $m-broken-no-verdict; fi; fi", "selftest-ok", 300),
Shell parsing consistency

The U1 shipped shells row has been updated to use variable-based echo for skip reasons, which may introduce inconsistencies in how skip markers are interpreted by the skipping() function. The previous version used a hardcoded string, while the new version uses $m-$sk-no-scripts, which could lead to unintended matching if the variables contain characters that match the skip pattern.

("U1 shipped shells", "m=shells; sk=skip; n=0; bad=''; for f in /usr/local/bin/1bit-* /etc/init.d/*; do "
                 "[ -f \"$f\" ] || continue; "
                 "case \"$(sed -n 1p \"$f\")\" in '#!'*sh) ;; *) continue ;; esac; "
                 "n=$((n+1)); "
                 "bash -n \"$f\" 2>/dev/null || bad=\"$bad $f:bash\"; "
                 "sh -n \"$f\" 2>/dev/null || bad=\"$bad $f:sh\"; done; "
                 "if [ \"$n\" -eq 0 ]; then echo $m-$sk-no-scripts; "
                 "elif [ -n \"$bad\" ]; then echo \"unparsable:$bad\"; "
                 "else echo \"shells-parse: $n scripts under bash and sh\"; fi", "shells-parse:"),
Inconsistent skip marker formatting

The U2 console heals row has been updated to use variable-based skip markers ($m-$sk-no-tui and $m-$sk-no-tty1), which may not be consistently formatted or interpreted by the skipping() function. This could lead to inconsistent behavior in how skip conditions are detected and handled.

("U2 console heals", "m=console-heals; sk=skip; "
                 "if ! pgrep -f 1bit-console-tui > /dev/null 2>&1; then echo $m-$sk-no-tui; "
                 "elif [ ! -w /dev/tty1 ]; then echo $m-$sk-no-tty1; "
                 "else printf '%s\\n' \"[  999.000000] amdxdna 0000:c6:00.1: [drm] *ERROR* stray line one\" "
                 "\"[  999.100000] amdxdna 0000:c6:00.1: [drm] *ERROR* stray line two\" > /dev/tty1 2>/dev/null; "
                 "sleep 2; scr=$(1bit-tty1 1 2>/dev/null); "
                 "n=$(printf '%s\\n' \"$scr\" | grep -cE '^ ?[1-9] [A-Z]'); stray=$(printf '%s\\n' \"$scr\" | grep -c 'stray line'); "

@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit 2eed8eb

@bong-water-water-bong
bong-water-water-bong merged commit 91f0d33 into main Nov 6, 2026
9 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the os/v3-vmcheck-selftest branch November 6, 2026 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant