An upgraded install never sees enrolment, so it can never connect again #66

Closed
opened 2026-07-31 08:20:36 +00:00 by aiko · 1 comment
Owner

Found on 2026-07-31 by launching the desktop app against a go-between running #26's gate — the first time anything ran the two halves together.

What happens

The app opens straight into the receiver chrome. Onboarding never draws, so there is no way to enrol, so the socket is refused forever. The server says so once per attempt:

WARNING | src.open_llm_vtuber.device_gate:admit:59 | A viewer connected and never identified itself

The client is not at fault at the wire: services/websocket-service.tsx:217 calls firstFrame(), gets null because there is no credential and no pending code, and correctly leaves the socket to the gate's timeout. It is doing the right thing with nothing to present. It is simply never shown the screen that would give it something.

Why

App.tsx:114 gates the entire enrolment path on one boolean:

if (!onboarding.done) {  <Onboarding />  }

and hooks/receiver/use-onboarding.ts:53 reads that from localStorage:

const [done, setDone] = useLocalStorage<boolean>(ONBOARDED_KEY, false);

Any device that went through setup under ADR-0004 has denpa.onboarded = true left in its webview store. On this machine the store also still held denpa.deviceId = 059ca39b-6455-4186-b7cb-f1c24d42ba61 — the old allowlisted id — and, of course, no token.

So the flag says "this device has been through setup" and it is true: the device went through the setup that existed at the time. ADR-0011 changed what setup produces, and nothing re-runs it.

The intent is already written down, three lines above the bug

use-onboarding.ts:49-52:

Whether this device has been through setup, which is not a claim about trust: the token is a credential the go-between checks on every connect, and a stored yes here opens nothing. A device whose token is refused is sent back to 周波数 whatever this says.

That last sentence is the fix, and it is not implemented. Nothing sends a refused device anywhere. Grep for ONBOARDED_KEY finds three hits — the constant, the import, the read — and no writer other than finish, so there is no reset path either. denpa#37's reset was assumed to survive ADR-0011 as "the deliberate way to re-enroll"; in the client it does not exist.

Why no test caught it

Every test starts from empty storage, where done defaults to false and onboarding draws correctly. The broken state is only reachable by upgrading, which no test does and no fresh device does. The Pad and the Pixel will not hit this — they have never onboarded. It bites exactly one population: installs that predate #26. That is why the whole board went green through it.

Same shape as the rest of this project's real defects: nothing throws, nothing fails, a screen just never appears.

Acceptance

  • A device with no credential is shown enrolment regardless of what denpa.onboarded says — the credential, not the flag, decides whether setup is needed
  • A device whose token is refused by the gate is sent back to 周波数, as use-onboarding.ts:49-52 already promises
  • A test starts from denpa.onboarded = true with no credential and asserts enrolment draws — the case no existing test covers
  • A test pins the refusal path: gate closes on a bad token → the app returns to enrolment rather than sitting on a dead socket
  • Decide and record whether denpa.deviceId should be cleared on enrolment; it is a dead ADR-0004 leftover that currently outlives the model that gave it meaning

Workaround used today

Stop the app, then delete %LOCALAPPDATA%\works.aiko.ollvt\EBWebView\Default\Local Storage\leveldb. That also drops denpa.pal.scale, which is cosmetic. Not a fix — nobody should have to know this.

ollvt-hermes-bridge#26, denpa#40, ADR-0011, denpa#37

Found on 2026-07-31 by launching the desktop app against a go-between running `#26`'s gate — the first time anything ran the two halves together. ## What happens The app opens straight into the receiver chrome. **Onboarding never draws, so there is no way to enrol, so the socket is refused forever.** The server says so once per attempt: ``` WARNING | src.open_llm_vtuber.device_gate:admit:59 | A viewer connected and never identified itself ``` The client is not at fault at the wire: `services/websocket-service.tsx:217` calls `firstFrame()`, gets `null` because there is no credential and no pending code, and correctly leaves the socket to the gate's timeout. It is doing the right thing with nothing to present. **It is simply never shown the screen that would give it something.** ## Why `App.tsx:114` gates the entire enrolment path on one boolean: ```tsx if (!onboarding.done) { … <Onboarding /> … } ``` and `hooks/receiver/use-onboarding.ts:53` reads that from `localStorage`: ```ts const [done, setDone] = useLocalStorage<boolean>(ONBOARDED_KEY, false); ``` Any device that went through setup under ADR-0004 has `denpa.onboarded = true` left in its webview store. On this machine the store also still held `denpa.deviceId = 059ca39b-6455-4186-b7cb-f1c24d42ba61` — the old allowlisted id — and, of course, no token. So the flag says "this device has been through setup" and it is *true*: the device went through the setup that existed at the time. ADR-0011 changed what setup produces, and nothing re-runs it. ## The intent is already written down, three lines above the bug `use-onboarding.ts:49-52`: > Whether this device has been *through* setup, which is not a claim about trust: the token is a credential the go-between checks on every connect, and a stored yes here opens nothing. **A device whose token is refused is sent back to 周波数 whatever this says.** That last sentence is the fix, and it is not implemented. Nothing sends a refused device anywhere. Grep for `ONBOARDED_KEY` finds three hits — the constant, the import, the read — and no writer other than `finish`, so there is no reset path either. `denpa#37`'s reset was assumed to survive ADR-0011 as "the deliberate way to re-enroll"; in the client it does not exist. ## Why no test caught it Every test starts from empty storage, where `done` defaults to `false` and onboarding draws correctly. **The broken state is only reachable by upgrading**, which no test does and no fresh device does. The Pad and the Pixel will not hit this — they have never onboarded. It bites exactly one population: installs that predate `#26`. That is why the whole board went green through it. Same shape as the rest of this project's real defects: nothing throws, nothing fails, a screen just never appears. ## Acceptance - [ ] A device with no credential is shown enrolment regardless of what `denpa.onboarded` says — the credential, not the flag, decides whether setup is needed - [ ] A device whose token is *refused* by the gate is sent back to 周波数, as `use-onboarding.ts:49-52` already promises - [ ] A test starts from `denpa.onboarded = true` with no credential and asserts enrolment draws — the case no existing test covers - [ ] A test pins the refusal path: gate closes on a bad token → the app returns to enrolment rather than sitting on a dead socket - [ ] Decide and record whether `denpa.deviceId` should be cleared on enrolment; it is a dead ADR-0004 leftover that currently outlives the model that gave it meaning ## Workaround used today Stop the app, then delete `%LOCALAPPDATA%\works.aiko.ollvt\EBWebView\Default\Local Storage\leveldb`. That also drops `denpa.pal.scale`, which is cosmetic. Not a fix — nobody should have to know this. ## Related `ollvt-hermes-bridge#26`, `denpa#40`, ADR-0011, `denpa#37`
Author
Owner

This was generated by AI during triage.

Merged to main in ee25aaa.

Fix. The credential decides, and it survives a dropped socket. use-onboarding.ts no longer treats a missing local flag as 'never enrolled', so an upgraded install that already holds a credential connects instead of being sent back through setup with no way forward. New services/origin.ts resolves the station the device enrolled against, so her model is served from that origin rather than a stale default.

Tests. use-onboarding.test.ts (+122) and services/origin.test.ts (+57). Gate on merged main: 62 files / 854 tests passing.

> *This was generated by AI during triage.* Merged to `main` in `ee25aaa`. **Fix.** The credential decides, and it survives a dropped socket. `use-onboarding.ts` no longer treats a missing local flag as 'never enrolled', so an upgraded install that already holds a credential connects instead of being sent back through setup with no way forward. New `services/origin.ts` resolves the station the device enrolled against, so her model is served from that origin rather than a stale default. **Tests.** `use-onboarding.test.ts` (+122) and `services/origin.test.ts` (+57). Gate on merged main: 62 files / 854 tests passing.
aiko closed this issue 2026-07-31 14:33:58 +00:00
aiko referenced this issue from a commit 2026-08-07 08:57:15 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#66
No description provided.