18 KiB
P8 Daemon (lgs-shell): independent review, round R1
Reviewer: an independent adversarial review agent. Date: 2026-10-07, 07:18 to 07:36 (Frame clock). Steam build 11094443 (/tmp/lgs/steam-build.json).
Scope: everything the builder reported for P8 at M3 / "complete": the card's builds, DM-1 to DM-6, the grace and plates selftests, REQ P4->P8, the contract contracts/daemon.md, and the offline harnesses.
Verdict: fix. There are three major findings and eight minor ones.
The acceptance tests are real. Every DM test, grace, plates and actions passes again on the live device, and the builder's offline harnesses reproduce exactly (39/39, 11/11). The problems are in the parts the tests do not look at:
lgs offleaves one of the daemon's globals in Steam (G-REMOVE fails).- The new theme-off dormancy keeps glassd running.
- A
--stayunit outlives a Steam restart.
The deployed files on the Frame are byte-identical to the tree (sha1 checked: lgs_shell.py 2fdeb0d6…, lgs_vr_core.js 0963bbe7…, lgs_vr.py 19ade4f7…, shell_ext/__init__.py 886ec091…).
1. What was re-run
All runs were on the Frame, from ~/.local/share/glass-shell/device, with python3 -B so that the review wrote no bytecode.
| Test | Command | Result | Agrees with builder? |
|---|---|---|---|
| shell_ext offline | lgs_shell.py selftest actions |
34/34 PASS | yes |
| DM-3 geometry | lgs_shell.py selftest dm3 (×3) |
1st run (07:20:41): 14/15. frameMenu.fWidth published 0.6338 against a fresh DumpLaserOverlays 0.124. 2nd and 3rd runs (07:21:38, 07:21:42): 15/15 |
mostly (m3) |
| DM-4 actions | flock /tmp/lgs/lab.lock lgs_shell.py selftest dm4 |
8/8 PASS. echo ok, unknown-type, bad-source, bad-args, rate; all logged; the bridge reply holds the last reply |
yes |
| DM-6 native=auto | selftest dm6, then selftest dm6 --live |
1/1 and 3/3 PASS: css-only (native=auto: the native gate has not passed (device/defaults.json has no native: on)), source builtin; start(auto) keeps a running unit |
yes |
| DM-5 page script | selftest dm5 --stop |
6/6 PASS: injected only into vr:systemui, removed when its file goes, re-injected, removed on stop |
yes |
| grace | selftest grace |
8/8 PASS: dormant with the same pid, resumed, binding, heartbeat and pages back | yes |
| DM-1 NAT | selftest dm1 |
15/15 PASS. lgs-native off at 2.6 s, nodes off at 11.8 s, lgs-native back 0.4 s after SIGCONT; teardown order correct; backoff [2, 4, 8, 16]; gives up after 5 exits; back to CSS only, nothing native left |
yes (but see m8 on "nodes back") |
| DM-2 v3 spec | selftest dm2 |
16/16 PASS. But "leaving plate and slab written with phase 0" took 2.92 s against a 3 s wait (the log says 0.44 s) | yes, flaky (m8) |
| plates (P6 v3 + acks) | selftest plates |
12/12 PASS: v3 reporter with fragments, profile, geom and ackMode; __LGS_MOTION present; plate in glassd.json, then drawn, then acked at 0.82 s; fade, then gone; nothing native left |
yes |
| Offline harnesses (PC) | the builder's scratch p8_offline.py, p8_steamloop.py |
25 + 39 PASS (offline), 11/11 PASS (steam loop) | yes |
| interactivePops | glass.py js --flags interactivePops "…bridge.get('daemon').profile…" |
wearer inside the step (heartbeat 1.7 s old), default after it |
yes |
| Reduce Motion (new) | a 1 Hz poll of shell.json during glass.py js --media reduce "<5 s wait>" |
The daemon logged Reduce Motion on 07:31:43 and off 07:31:50, so it follows Steam's media query within 1 s (P-56 input to glassd and the spec) |
builder had no live test |
| G-REMOVE (new) | scratch p8rev_remove.py under native.lock → lab.lock → lab-vr.lock: lgs.py off, sweep, lgs.py on --css |
After off: no unit, no glassd, no lgsAction, no __LGS_NATIVE. SteamVR pages: no __LGS_VR, __LGS_VRX, #lgs-theme, lgs-on, no LGS Inter FontFace, no __LGS_SG/__LGS_MOTION. But window.lgsLayers is still a function in SharedJSContext (M1) |
no |
| Binding leftover (new) | scratch p8rev_binding.py (same locks): stop, delete window.lgsLayers, start CSS only, stop again |
After the second stop, typeof window.lgsLayers = function (function () { [native code] }), lgsAction = undefined. So the daemon's own stop leaves it, in CSS-only mode too |
no |
| Dormancy in native mode (new) | scratch p8rev_dormant.py (same locks): start(glassd=fakeglassd, native='on') with the real reporter, lgs.op('off'), sample, lgs.op('on'), back to --css |
Dormant: reporter stopped, glassd.json surfaces [], no lgs-native, no nodes. glassd pid 147988 still alive at +1 s and +11 s (and the same pid on resume). Status mode = starting while dormant (M2, m7) |
no |
--stay and a Steam restart (new, offline) |
scratch p8rev_stay.py = the builder's p8_steamloop.py fake world with Shell(stay=True) |
New SharedJSContext target with the theme off: stops [], dormant, the task keeps running. Steam unreachable past STEAM_GONE_S: stops [] (M3) |
no |
| Idle cost (new) | /proc/<pid>/stat over 30 s; ss TIME_WAIT counts; DumpLaserOverlays timed ×5 in vr:systemui |
CSS only: 27 ticks per 30 s (0.9 % of a core); 81 TIME_WAIT sockets to 8090 in about 60 s; DumpLaserOverlays 101–105 ms every 5 s (m4) |
— |
| SteamVR font (visual) | glass.py shot vr:controllerbindingui p2_p8rev_bindingui (viewed; moved out of shots/) |
Labels render in LGS Inter ("Show More Applications": Inter's letterforms, not Motiva) | yes |
2. Findings
Major
M1. lgs off leaves the daemon's window.lgsLayers binding in Steam (G-REMOVE, DoD 4).
- The daemon calls
Runtime.addBindingfor bothlgsLayersandlgsActionevery time it connects, in CSS-only mode too (lgs_shell.py1823-1824). ThelgsLayersbinding is useless in CSS-only mode, because no reporter is injected. - Teardown deletes only
lgsAction(2654:for (const n of ['lgsAction'])). Runtime.removeBindingfrom the fresh teardown session does not remove the function that the closed session installed, sowindow.lgsLayers(native code) stays in SharedJSContext until Steam restarts.- Proven twice:
- In the G-REMOVE run, P1's own
offsweep listslgsLayersinleftovers.globals. - In the binding experiment, after deleting it the daemon's start re-adds it and its stop leaves it.
- In the G-REMOVE run, P1's own
- Fix:
- Delete both binding globals in teardown.
- Add the
lgsLayersbinding only whennative_possible(). - Add a
typeof window.lgsLayerscheck to the teardown selftests.
M2. In native mode, glassd keeps running for the whole theme-off grace (up to 600 s).
- Contract §1 says the native layer is "taken away" while the daemon is dormant, but nothing stops glassd:
supervise_glassdkills it only whennot native_possible().glassd_loopcheckstheme_ononly before a start.
- Live (fakeglassd, the real reporter): the same glassd pid is alive at dormant +1 s and +11 s, with
glassd.jsonsurfaces[]. - With the real binary, an empty visible set while the dashboard is shown keeps:
- a ~250 Hz loop;
- one-frame camera shots every 5 s (
glassd.cpparound 3036-3048,--idle-hz0.2).
- So after a theme off without
lgs off, a camera reader runs for up to 10 minutes with nothing to draw. Before session 2 it was gone after 3 s. This is a regression of the "nothing runs at idle" bar. - Fix:
- Kill glassd when the daemon goes dormant (
glassd_loopalready waits fortheme_onbefore it restarts glassd). - Or keep it for a short warm window (≤ 30 s), then kill it.
- Add a glassd-pid check to
selftest gracein native mode.
- Kill glassd when the daemon goes dormant (
M3. A --stay unit survives a Steam restart and the loss of Steam, with no time limit (hard rule 7, contract §1).
- In
steam_loop, the "new SharedJSContext target with the theme off, so exit at once" rule sits only in thenot self.staybranch (1856-1866). - The
STEAM_GONE_Sexit hasand not self.stay(1809). - The offline fake-world run with
stay=Trueconfirms both: no stop, dormant forever. glass.py native-sessionstarts the daemon with--native --stay. If a native session's client dies before itsfinally(for example the usage-limit cut-off that ended every agent at 04:30), the unit stays in native mode with glassd running. It then outlives a Steam restart, keeps re-theming SteamVR pages and never exits until someone runslgs on --cssorlgs off.- Contract §1 promises "at once, so nothing outlives a Steam restart" with no exception for
--stay. - Fix:
- Apply the Steam-restart exit and the
STEAM_GONE_Sexit with--staytoo. - Give
--staya ceiling, for example 1800 s (the native lock's own wait).
- Apply the Steam-restart exit and the
Minor
m1. The daemon writes bytecode next to the code on the Frame.
- The unit runs
/usr/bin/python3 -u lgs_shell.py daemon …, without-B, and its environment has noPYTHONDONTWRITEBYTECODE(read from/proc/<pid>/environ). device/shell_ext/__pycache__/__init__.cpython-312.pycappeared at 04:22:56, two minutes aftershell_ext/__init__.pywas synced.device/__pycache__/lgs_shell…pyc,lgs…pycandlgs_vr…pycexist too.- Plugins loaded by
importlibwould get.pycfiles as well. - These files are inert, but they are written outside
/tmp/lgsand/dev/shm/lgsand survive a reboot. P1'sdevice/lgswrapper goes out of its way to avoid exactly this. - Fix:
python3 -B -uin thesystemd-runcommand line, andsys.dont_write_bytecode = Trueat the top oflgs_shell.py.
m2. One malformed report field kills the daemon.
- Several conversions sit outside any
try:- in
glassd_config:float(L.get("r") or 0),x,y,dz, the surface'stexW/texH/radius, andround(nan); - in
build_spec:float(L.get("dz") or 0.012).
- in
- Offline, all 4 of 4 probes raise (
'12px','1e',[1],'wide'). Each raise goes through every loop'sbug()and becomes fatal after 5 in 120 s, so the whole daemon exits, including SteamVR page upkeep and the actions relay. - P6's reporter coerces its numbers, so this cannot be reached today. But contract §7 promises "type checks", so use
_num()for every numeric field and drop the bad item.
m3. Geometry can be published up to about 5 s stale, and DM-3 is not atomic.
fWidth/fHeightrefresh only with the 5 sDumpLaserOverlays, unlessuv,mpporvisiblealso change.- The first DM-3 re-run failed on
fWidth(0.6338 published against 0.124 live). - DM-3 compares values taken up to about 7 s apart (
shell.jsonevery 2 s, plus the dump), not "in the same step". - Suggestion: dump when the PooledPopup's
rectchanges (SteamVR logs[PooledPopups] Showing … rect), and have DM-3 wait for a freshgeom.at.
m4. Idle work in CSS-only mode.
- 0.9 % of a core.
vr_theme_loopopens a fresh devtools websocket per SteamVR page every 1.5 s (eval_once; about 1.3 new 8090 sessions per second).DumpLaserOverlays(about 100 ms round trip) runs every 5 s forever.- This is inherited from Phase 1's watcher, but P8 owns it now. Keep one session per page, and dump only on change.
m5. start() is not serialized.
- Another agent's lock-free
lgs reload(07:23:13) raced my lockedlgs.py on --css. Mine returnedfailed: Failed to start transient service unit: Unit lgs-shell.service was already loaded, although a unit was running. - The two runtime loads in parallel also left P1's runtime off: "rt/07-tooltip.js failed …
_loading", "load failed, CSS only" (see §5). - Fix: treat "already loaded" as running (re-read
unit_info()), or take a short start lock.
m6. Stale text.
contracts/daemon.mdline 7 and thelgs_shell.pydocstring (38-41) still say the unit "exits when the Steam theme goes off".- The docstring's selftest list (60-61) lacks
dm1,dm2,graceandplates. - Contract §11 does not list
plates. docs/LAB.mdline 115 (V1) says the SteamVR watcher exits when the Steam side is turned off. The open REQ P8->V1 covers only NATIVE.md; extend it to LAB.md.
m7. Status during dormancy.
- While dormant in native mode,
status.modeand the bridgedaemon.modesaystarting. - They should say
dormant(withnative: false), so that areas and the lab can tell a dormant daemon from one that is starting.
m8. Test strength.
- DM-1's "nodes back" accepts
items > 0withoutexpired === false, which is why it reports 0.0 s. - DM-2's phase-0 wait (3 s) is about equal to the measured 2.92 s, so it is flaky.
dm3anddm4take no lab lock.dm5 --stopanddm6 --liverestart the daemon withoutlab.lock, under other agents' Steam steps.- DM-4's "echo plugin" is the built-in echo, so the live path that loads a plugin file (
device/shell_ext/*.py, scanned by the running daemon) is only tested offline. A RAM test plugin folder (like/tmp/lgs/vr-scripts) would close that gap.
Accepted deviations (checked, no finding)
- Dormancy is bounded and the user's path is unaffected.
lgs toggle→lgs.py off→op('off', vr=True)stops the unit first. Dormancy only happens on lab--stockor P1 tests, or on a Steam UI reload, and it ends at 600 s (shellThemeGraceS, 0..3600). The journal shows other agents'--stocksteps surviving it (07:17:32, 07:17:47). M2 and M3 are about what runs during it, not about the grace itself. native=autofollows the plan. The order is/tmp/lgs/flags.json, thendevice/defaults.json, then CSS only. The flags.json override is PLAN §1.17 S1's flag. The coordinator's note holds: withoutdefaults.json, the daemon runs CSS only, with the reason given.- The actions relay is strict and fails closed. The checks are the source context (nonce probe), the claimed
src, the exact argument schema, the flag, the per-type rate, the global 20 per 10 s, and one call in flight.navandlaunchare dry underactionsDryRun, under P1's action logger (on in every lab step), and whenever the logger state cannot be read. No launch-type action exists yet, and tests use only echo. - REQ P4->P8 is done. In the G-REMOVE run,
LGS Inter loadedwas present insystemui,controllerbindinguiandkeyboard, and was gone afterlgs off. P8's edit towp/P4.mdis the sanctioned REQ answer. - Ramps match the motion tokens. 250/350, 735/514 and 180 ms equal
motion_tokens.h(kMatInMs,kMatOutMs,kSheetIn,kSheetOut,kReduceMs). They are hard-coded copies, so they could drift; they match today. - RAM leftovers are harmless.
/dev/shm/lgs/shell.jsonstays afterlgs offwith modestopped, and/tmp/lgs/vr-scripts/stays as an empty folder. Both are RAM-only and harmless.
3. Visual review (against "feels native visionOS, no outlines")
P8 draws no surface of its own. Its visible effects are the SteamVR font, the materialize policy and the depth inputs (unitM, profile, reduceMotion).
- The SteamVR font fix works. In the
vr:controllerbindinguishot, the binding UI's labels are set in LGS Inter. - Observations for C5b, not for P8: the same page still uses Steam's all-caps bold titles ("EDIT CONTROLLER BINDINGS"), and its panel shows a light top rim along the rounded corners. Both conflict with the "no outlines" bar.
- Native glass was not looked at with
hv(no room frames were taken). P9's R1 review covered the live plates end to end through P8, and DM-2 /platesshow the policy fields arriving.
4. Checklist (a)–(f)
- (a) Ownership: pass. The files changed since
f5190bbaredevice/lgs_shell.py,device/lgs_vr_core.js,contracts/daemon.mdandwp/P8.md(all P8's), pluswp/P4.md(the REQ answer). There are no untracked P8 files, anddevice/vr/andshell_ext/hold nothing new. - (b) Safety: fail (M1, M3, m1).
- Transient unit (
systemd-run --user --collect); nothing autostarts. - The never-list is respected; the tests use only echo, SIGSTOP and SIGKILL of the daemon's own children, and fakeglassd.
- Locks are mostly held (m8).
- But
lgs offleaves a global (M1), a--stayunit outlives a Steam restart (M3), and bytecode is written outside the runtime folders (m1).
- Transient unit (
- (c) Robustness: fail (M2, m2, m4).
- Freeze recovery is 0.4 s, the backoff and give-up work, timeouts are tolerated, and the page scripts, fonts and scene graph are removed on stop.
- But glassd runs through dormancy (M2), a malformed report is fatal (m2), and there is idle polling (m4).
- (d) Conformance: pass for P8's inputs. P-56: Reduce Motion is followed within 1 s and the ramps are 180 ms. Depth uses
unitM= S × r (DM-3). P-71's ≤ 350 ms out-ramp holds for liquid and panel glass; at launch the dashboard hides and P8 keeps the layout rather than animating it. - (e) Function retention: pass. The daemon removes no Steam path.
lgsActiononly adds paths, and callers time out after 2 s when no daemon is running (contract §5). Interactive pops stay off unlessinteractivePopsis set. - (f) Visual quality: pass for P8's part (the font). The binding UI's own look is C5b's (§3).
5. Incidents during the review
- Runtime left off by a race, then restored. At 07:23:13, my locked
lgs.py on --css(the G-REMOVE restore) raced another agent's lock-freelgs reload. The runtime load failed ("Cannot read properties of undefined (reading '_fail')"), leaving__LGS_RTabsent. I restored it withflock /tmp/lgs/lab.lock python3 -B lgs.py reload(07:24): runtimerunning, react installed. - Device left as found.
- Theme on, runtime loaded,
lgs-shellCSS only (--req off), not dormant, no glassd. /tmp/lgs/flags.jsonheld{"wp.p3": true}from another agent's step in progress, not mine.- No menus were opened and there was no synthetic hover.
- Theme on, runtime loaded,
- Review files cleaned up.
- The review scripts in
/tmp/lgs/p8rev/were deleted on the Frame. - The shot
p2_p8rev_bindingui.pngwas moved out ofshots/into the reviewer's scratch folder. It shows no room and no personal names. - The tree was not changed apart from this file.
- The review scripts in