Repository navigation
os/v3: the appliance announces 1bit-os.local, and a blank-key check stops asking for a key - #953
Conversation
… healthy server is not reported dead (#945) `#934` split the lemonade row into a blank branch (the shipped cycle-20 default) and a keyed branch. The keyed branch assigned `MODELS`; the blank branch assigned only `BODY` and `WITHOUT`, and `MODELS` is the ONE input every row below reads (`models`, `have_model`, `have_model_ci`, `pick`, and therefore `chat`/`chat-hrx`/`chat-npu`/`bench`). On the appliance that made a live server report `FAIL Lemonade did not answer` and skip every chat row: the c20 board was 19/1 with `u4-no-hw-skips` downstream of it. Root cause is the split itself, not the missing line: a mode can set the probe body and forget the list. So the list is now chosen in ONE place, by a pure selector, after the mode branch: MODELS=$(models_list "${MODELS:-}" "$BODY") `models_list` takes the keyed body when there is one and the keyless body otherwise - which IS the list in blank mode (`/api/v1/models` answers the same records keyless when a key is not configured). Before / after, driving the script's real lemonade block with the network stubbed (healthy server, cycle-20 blank mode): before PASS lemonade answers ... without a key FAIL models Lemonade did not answer, so the model list cannot be checked MODELS=[] after PASS lemonade answers ... without a key MODELS=[{"data":[{"id":"user.Qwen3-0.6B-NPU-1bit",...}]}] models row: proceeds with 61 bytes of model list and keyed mode still takes the authenticated list (not the keyless one). Self-test: three new arms in `--self-test` (`models_list` blank / keyed / nothing answered) - the selector is pure for the same reason `lemonade_verdict_blank` is. Negative control: deleting `models_list` fails all three arms (`command not found`, rc 1); `bash -n` and the pre-existing 24 arms are clean on both sides (`release-check self-test PASS`). `tools/copyright.py --check` rc 0, no new files. Closes #945. Not claimed: the appliance-side board. The A/B on the frozen c20 image is in the issue; this fixes the branch that produced it and is proven device-free above.
…tops asking for a key Item 10 (owner 2026-11-04 ~01:3x: clickable links via ryzen) plus item 9a's other half and 9b from the c20 board (TOS3-C18-items.md), stacked on #951 for the model-list half of 9a. 10 mDNS: the built rootfs carries libavahi-client/common and no /usr/sbin/avahi-daemon, and nothing in the image answers an mDNS query, so http://1bit-os.local:13305 could never work. Adds the avahi-daemon + libnss-mdns package rows, /etc/init.d/18onboard-mdns (starts the D-Bus system bus first - PID 1 is not systemd, and avahi exits without a bus), the mdns4_minimal nsswitch line, and container-build.sh refusals for both halves. vm-check gains a 'U2 mdns' row. 9a the header half #951 does not cover: blank mode (the shipped default) sent 'Authorization: Bearer none' on the chat, bench and NPU requests. Blank mode's contract is stock Lemonade - no header at all, which is the shape the board measured (header-less /models 200, NPU long then short, 0 TDR). auth_get/auth_status/auth_post build the header only when the machine has a key, and `lemonade bench` gets LEMONADE_API_KEY unset rather than a placeholder. 9b prepare-stick says what the medium can answer: a stick with the NPU model and no GGUF has nothing for chat-hrx to ask, which is how the c20 stick shipped. vm-check's 'U4 release-check' row now fails on the RESULT line the board reads instead of recording it as INFO - that row is what let 9a through the VM gate. closes #952
PR Reviewer Guide 🔍(Review updated until commit 34702f8)Here are some key observations to aid the review process:
|
…s any script that names the bus
Measured on the c20 image itself (its puppy sfs, extracted): /etc/rc.d/rc.services does
grep -qF dbus-daemon $service_script || $service_script start
so a script whose TEXT contains that string is skipped outright - and comments count, because it greps the file.
18onboard-mdns started the D-Bus system bus itself, so it named the binary, so it would have been skipped and
1bit-os.local would have been silently unannounced on every boot. The trap's shape is the bad one: nothing fails.
The premise was wrong too, which is what makes the fix smaller rather than bigger: rc.sysinit already starts the
bus (`--system`, line ~695) BEFORE it runs rc.services (line ~727), and the board's own messages.log proves a live
bus (it was trying to activate org.freedesktop.login1). So the service now only WAITS for /run/dbus/system_bus_socket
(bounded, 10 s) and starts the responder; the dead bus-starting branch and the now-unreachable fallbacks are gone.
The gate is in the build, not in a comment: container-build.sh reads the PACKED etc/init.d/18onboard-mdns and refuses
the image if it is missing or if rc.services' own rule would skip it. My first attempt at this fix still tripped it
(one comment line spelled the path), which is the assertion doing its job.
Checked in a sandbox (unprivileged unshare, no device, host /run,/var,/usr untouched), three arms: no socket -> says
so and starts nothing; socket + avahi ok -> "avahi-daemon up" (announced); socket + avahi failing -> logs it, boot
continues.
|
Persistent review updated to latest commit c49946f |
cfb7dfe to
c49946f
Compare
|
Preparing review... |
|
Persistent review updated to latest commit c49946f |
|
#953 ready for review at c49946f. Stacked on #951 (100867d) - merge that first, then this rebases to 5 files. Item 9a's model-list half is #951's (I dropped mine rather than duplicate it); this carries the header half it misses, plus 9b and item 10 (closes #952). |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY on content at c49946f96 — but #953 contains #951's whole diff, so they are not both mergeable as they stand.
Merge order, first
#961's fix aside, the os/mini/release-check.sh hunks in this PR are the same change as #951 (models_list, the three self-test arms, MODELS=$(models_list "${MODELS:-}" "$BODY")). #951 is a strict subset: this PR has all of it plus the auth_get/auth_status/auth_post header change, the mDNS init script, prepare-stick item 9b, container-build.sh and vm-check.sh.
So: merge #953 and close #951 as superseded, or merge #951 first and rebase #953 onto it (git will drop the duplicate hunk). Landing both as they are means the second one conflicts over text that is already identical.
What I verified here
bash -nclean on every shell file it touches; the new18onboard-mdnsinit script included.- The
models_listhalf I already verified on #951 by running it: blank mode gives the keyless list, keyed mode is unchanged, and two negative controls (definition removed; keyed precedence dropped) both turn the self-test red with rc 1. - The blank-mode auth change is the one with behavioural risk, and the reasoning is right: cycle 20 sent
Authorization: Bearer none, and the board's own evidence is that a header-less request is what answers 200 and keeps the NPU TDR-free. Keeping the keyed and blank shapes in three small helpers rather than at every call site is the correct way to stop them drifting. prepare-stickitem 9b isokin both branches, deliberately: an NPU-only medium is legitimate, so this is a statement about what the stick can answer, not a pass/fail.
Not run
The mDNS announcement and the container-build/vm-check rows need a real image build and a boot, which is the appliance lane's gate, not something I can do on this box. packages-extra only adds the avahi/mDNS package lines and container-build.sh gains the checks around them; both are read, not executed.
…R for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency.
|
Persistent review updated to latest commit 6de2eee |
|
Preparing review... |
…R for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency.
|
Persistent review updated to latest commit 5aae34c |
|
Persistent review updated to latest commit 3db4549 |
|
Persistent review updated to latest commit 07ffab9 |
|
Persistent review updated to latest commit 407f224 |
|
Persistent review updated to latest commit 5c5994c |
|
Persistent review updated to latest commit 34702f8 |
…R for phones, and the mDNS name (#962) * os/v3/puppy: the console TUI is usable over ssh - clickable URLs, a QR for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency. * os/v3/puppy: pin the QR's rows as a known answer (pi-prs' nits on #962) Review: "the one sentence in the module that would make a reader trust the drawing is the one thing the suite does not do" (nit 1), and nit 2's "worth keeping the comment that says the matrix is the encoder's and not ours". Nit 1 was right about the phrase, not quite about the coverage: `blocks()` did have a known-answer test (a hand-drawn 2x2), but its name (`two_module_rows_become_one_text_row`) did not say so and the doc comment pointed at "a known-answer test" without naming it - so the reader was asked to take it on faith. Both halves are fixed: - `two_module_rows_become_one_text_row` -> `blocks_draws_two_module_rows_per_text_row` (the drawing rule now reads in the name). - New `a_known_url_renders_to_these_exact_rows`: `code("http://192.168.50.110:13305")` against the 17 rows of 33 cells written out literally. That is the known answer rather than the fit check (`the_appliances_own_url_fits_the_block_the_layout_reserves`, kept - it is about the layout's box, not the drawing). A change to the encoder, the ECC level (L), the version (2) or the quiet zone now fails the build instead of shipping a code nobody can read; the literal block also makes the quiet zone and the half-block rule visible in the test itself. Nit 2, kept and made explicit: the module doc now says the matrix is the encoder's, that the drawing is what this module owns, that a scan is not asserted by any test - and how it WAS measured: the 240x67 frame rendered to a PNG and read back with zxing-cpp returned `http://192.168.50.110:13305` (2026-11-04), with "re-run that by hand if the encoder/ECC/quiet zone changes". The README says the same next to the feature. Measured on this head: cargo test 43/43 (1 new), clippy --all-targets -D warnings clean, fmt --check clean, tools/copyright.py --check rc 0, build-static.sh ok (static musl binary fc71d2c720f97213, 1550360 B). The two snapshots are untouched by this commit - no drawn cell changes. --------- Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
The page told a reader 'mDNS is not claimed' and to read the address off the box. Cycle 21 item 10 (#953, merged b8a07df) ships the responder, asserts it at build time and prints it in the console's CONNECT block, so that sentence is now false in the direction that costs a reader a step. Said what the sources say, with the two limits kept: the announced name is $(hostname).local (so it follows a rename), the CONNECT row says when avahi is not running, and a peer resolving it over multicast is still not claimed on this page.
… announced but never answers (#1016) * os/v3/puppy: the firewall drops every mDNS query, so 1bit-os.local is announced but never answers The c22 board (4 Nov 12:5x AST) logged `avahi-daemon up: 1bit-os.local is announced on this network` and a raw mDNS query from ryzen got no answer. #952/#953 put the responder in the image; what was never opened is the path for a *query*, and the cause is the same `rc.firewall` that `zz-onboard-ports` already works around for tcp. Read out of the packed c22 puppy SFS, `etc/init.d/rc.firewall`: -P INPUT DROP -A INPUT -p ALL -d 224.0.0.1 -j DROP -A INPUT -p UDP -j udp_inbound (drops 137/138, accepts DHCP 67->68, then -j RETURN) -A INPUT -m pkttype --pkt-type broadcast -j DROP -A OUTPUT -p ALL -j ACCEPT so every NEW inbound UDP packet falls through to the DROP policy, and a query on 5353 is exactly that; the appliance's own announcements leave fine, which is why avahi reported success while nothing answered. This file already opens tcp/13305 and tcp/22 for the same reason - mDNS is the UDP half. LAN-only, the same ranges hosts.allow already names below plus link-local for avahi's no-lease fallback, ahead of udp_inbound and the broadcast drop, idempotent via -C, and in `zz-onboard-ports` rather than `18onboard-mdns`, whose name sorts before `rc.firewall` and would be wiped by it. Measured in a booted c22 image (`~/wt/os3i/c23-mdns-probe6.sh`, one controlled announcement burst per arm, the rules' own packet counters): arm policy DROP the fix's INPUT rule shipped firewall 5 -> 12 (+7/2095) absent + one `-I INPUT 1 ... dport 5353` 12 (unchanged) 0 -> 7 packets / 2095 bytes rule removed again 12 -> 19 (+7/4963) absent the same seven packets that were dropped are the ones the rule accepts, byte for byte. Two caveats stated rather than implied: those packets are avahi's own multicast announcements looped back (IP_MULTICAST_LOOP), not a query from a peer - same packet class, same chain, and a peer's query is what the board row must still prove; and QEMU user networking cannot carry inbound UDP at all (a `hostfwd=udp` delivers nothing even with the firewall flushed), which is why this is measured on the appliance's own interface. Gate: a `U3 mdns rule` row in vm-check.sh that fails an image without the rule. closes #1011 * os/v3/puppy: a self-check for the inbound rules, so the mDNS block cannot regress silently `mdns-ports-selftest.sh` runs the shipped `zz-onboard-ports` against a stub `iptables` (a rule table in a file, `-C` answered from it): no root, no ruleset, no device. It asserts four `udp/5353` inserts with the four intended sources, that a second run inserts nothing, and that the two TCP ports this file already opened are still opened. Measured both ways before committing: shipped script run 1: 6 inserts, 4 of them udp/5353 run 2: 0 inserts, 4 -C -> RESULT: PASS origin/main's run 1: 2 inserts, 0 of them udp/5353 -> RESULT: FAIL `sh -n`, `shellcheck -S warning` and `tools/copyright.py --check` are clean. --------- Co-authored-by: pi-os3 <pi-os3@1bit>
Cycle 21 items 10, 9a's header half and 9b (c20 board,
TOS3-C18-items.md) plus pi-release'sTOS3-NOTE-c20-prepstick.md.Stacked on #951 (
100867df), which owns item 9a's model-list half (#945) with its own self-tests — merge #951 first; after it lands this rebases to 5 files.closes #952
Root causes, measured
Item 10 — nothing in the image can answer an mDNS query. Measured in the built resolute64 rootfs:
libavahi-client/libavahi-commonare present,/usr/sbin/avahi-daemonis not, andnsswitch.confhas nomdnsline. Sohttp://1bit-os.local:13305(owner: "clickable links via ryzen") could never resolve. Two halves: the responder (package row +/etc/init.d/18onboard-mdns) and the client half (libnss-mdns+mdns4_minimal).container-build.shrefuses an image missing either.The responder's own trap, found by extracting the c20 image's puppy sfs (
c49946f):/etc/rc.d/rc.servicesruns each/etc/init.dscript withstartonly aftergrep -qF dbus-daemon "$script"— a script whose text names the bus binary is skipped outright, comments included. The first version of this service started the bus itself, so it named the binary, so it would have been silently skipped on every boot and1bit-os.localwould never have been announced. The premise was wrong too:rc.sysinitalready starts the bus (--system, line ~695) before runningrc.services(line ~727), and the board's ownmessages.logproves a live bus. So the service now only waits (bounded, 10 s) for/run/dbus/system_bus_socketand starts the responder.container-build.shapplies rc.services' own rule to the packed script and refuses the image if it would be skipped — my first attempt at the fix still tripped it (one comment line spelled the path), which is the assertion doing its job. Sandbox check, three arms: no socket → says so, starts nothing; socket + avahi ok → "avahi-daemon up"; socket + avahi failing → logged, boot continues.Item 9a, header half — blank mode asked for a key it does not have. The shipped default is blank key (owner, c20: "blank key"), whose contract is stock Lemonade: no header at all. Cycle 20 sent
Authorization: Bearer noneon the chat, bench and NPU requests to keep one code path. #951 fixes the model list but not this.auth_get/auth_status/auth_postbuild the header only when the machine has a key;lemonade benchgetsLEMONADE_API_KEYunset, not a placeholder.Item 9b — the medium said nothing about what it cannot answer. The c20 stick carried the NPU model and no GGUF, so
chat-hrxhad nothing to ask.prepare-stick --checknow prints that as its own line with the flag that fixes it (no GGUF in models/ - chat-hrx has nothing to ask; --model <file.gguf> stages one).Why the VM gate let 9a through.
vm-check'sU4 release-checkrow wasINFO: it printedFAIL modelsand passed. It now assertsRESULT: PASS— the same line the board reads.Before / after, on the request log
~/wt/os3i/rc-blank-repro.shruns the realrelease-check.shagainst a stub lemond in an unprivileged sandbox (CPU-only, no device, no/datatouched) and records every request's Authorization header plus the bench row's environment.origin/main)FAIL models,SKIP gguf,SKIP chat,SKIP benchGET /models— no POST at allPASS models,PASS chat (1BIT OK @42 tok/s),PASS bench,SKIP gguf(no GPU in the sandbox)POST /chat/completions auth=<none>GET/POST … auth=Bearer 70f057…Blank mode sends no header and still answers; keyed mode still sends the real key.
release-check.sh --self-test: PASS (includes #951's threemodels_listcases).Gates
bash -n/sh -nclean,tools/copyright.py --checkrc=0, no new shellcheck warning per file vs the base (release-check.shstays at its 5 pre-existing). The new fileos/v3/puppy/embed/etc/init.d/18onboard-mdnscarries the full Apache notice.Not claimed: that avahi actually starts under Puppy's init in a booted image, and that a peer on the LAN resolves
1bit-os.local— the first is theU2 mdnsvm-check row, the second needs multicast from outside the guest, which QEMU user networking does not carry. Both go to the board.