Files
UmbrelApps/Docs/Plannen/Actief/008-Umbrelapp/PROGRESS.md
T
HarmenandClaude Opus 5 a092464e4d Relay 0.7.0: een weg naar binnen, en de melding bij elke klik weg
Vier punten van de gebruiker, opgekomen tijdens het beproeven met de testclient.

De getimede leerstand is eruit; de wachtlijst is de enige weg naar binnen. Zijn
redenering: allebei de wegen vragen iemand die bij de app kan, dus het is
dubbelop, en het venster is de zwakste omdat het iedereen toelaat die er
toevallig in verbindt. Dat weegt zwaarder nu de relay op een publiek wss-adres
kan staan. De oorspronkelijke reden voor de leerstand, dat je je eigen OwnerId
nergens kon aflezen, verviel toen de weigerlijst dat id ging tonen. Daarmee
verdwijnt ook de bug die hij dezelfde dag meldde: een geleerde eigenaar bleef in
de weigerlijst staan terwijl hij al kon schrijven en lezen, want decideOwner
haalde hem niet van die lijst af en de knop allow wel.

"Refused owners" heet "Waiting list", met de badge Waiting en een teller waar de
widget van het tijdvenster stond. Het veld op schijf blijft rejected: hernoemen
zou een migratie zijn voor een woord dat niemand ziet.

Het adres onderaan zei http:// en dat kan nergens werken, want de relay spreekt
WebSocket en nooit HTTP. Nu ws://<host>:3852, met een regel over wss://<domein>
zonder poort achter een reverse proxy. Dat is precies de fout die diezelfde dag
een ronde kostte bij het koppelen van de testclient.

De melding bij elke klik is weg. Die stond in de gewone stroom van de pagina,
dus alles eronder schoof omlaag en weer omhoog. Nu gaan de knoppen in de lijsten
even op slot tot de ronde de nieuwe stand heeft; foutmeldingen blijven wel staan,
want die zeggen iets wat je nergens anders ziet.

STATE_VERSION blijft 1 en een owners.json van 0.6.0 leest door: learning en
learningUntil worden gelezen, genegeerd en niet teruggeschreven. Een verhoging
zou store.js de allowlist van een werkende installatie opzij laten schuiven.

Twee toetsen bewaken dat de leerstand niet terugsluipt: een onbekende eigenaar
wordt geweigerd ook met learning: true in het bestand, en set-learning is een
onbekende actie. Beide mutatie-getest.

De compose staat op 0.7.0 zonder digest, zodat het hard faalt tot de image
bestaat. Bouwen, duwen en pinnen ligt bij de gebruiker.

Suite: 424 goed, 0 fout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 15:45:55 +02:00

303 lines
21 KiB
Markdown

# Voortgang - Umbrelapp
> Chronologisch sessielog, nieuwste bovenaan. Kort: 3 tot 6 regels per entry. Wat er is gebeurd en waarom,
> niet wat er nog moet: dat staat in [TAKEN.md](TAKEN.md).
## 09-09-2026 - 0.7.0: één weg naar binnen, en de melding bij elke klik weg
Vier punten van de gebruiker, opgekomen tijdens het beproeven met de testcliënt. Drie ervan waren
eenduidig, de vierde was een ontwerpvraag die hij zelf stelde en die ik met hem beantwoord heb.
**De getimede leerstand is eruit; de wachtlijst is de enige weg naar binnen.** Zijn redenering: allebei de
wegen vragen iemand die bij de app kan, dus het is dubbelop, en het venster is de zwakste omdat het
iederéén toelaat die er toevallig in verbindt. Dat weegt zwaarder nu de relay op een publiek wss-adres kan
staan. De oorspronkelijke reden voor de leerstand (je kon je eigen `OwnerId` nergens aflezen) verviel toen
de weigerlijst dat id ging tonen. Daarmee verdween ook een bug die hij dezelfde dag meldde: een geleerde
eigenaar bleef in de weigerlijst staan terwijl hij al kon schrijven en lezen; `decideOwner` haalde hem niet
van die lijst af en de knop "allow" wel.
**"Refused owners" heet "Waiting list"**, met de badge "Waiting" en een teller die de widget van het
tijdvenster vervangt. Het veld op schijf blijft `rejected`: hernoemen zou een migratie zijn voor een woord
dat niemand ziet.
**Het adres onderaan zei `http://`** en dat kan nergens werken, want de relay spreekt WebSocket. Nu
`ws://<host>:3852` met een regel over `wss://<domein>` zonder poort achter een reverse proxy. Dat is
precies de fout die diezelfde dag een ronde kostte bij het koppelen van de testcliënt.
**De melding bij elke klik is weg**, want die duwde de hele pagina omlaag en weer omhoog. In plaats
daarvan gaan de knoppen in de lijsten even op slot; foutmeldingen blijven wél in de balk staan.
`STATE_VERSION` blijft 1 en een `owners.json` van 0.6.0 leest gewoon door. **Nog niet gebouwd:** de compose
staat op `0.7.0` zonder digest, zodat het hard faalt tot de image bestaat.
## 07-09-2026 - de eigenaars op volgorde van laatst gezien
Op verzoek van de gebruiker: de rijen stonden in de volgorde van het bestand, dus van binnenkomst, en dat
zegt na een week niets meer. Nu meest recent gezien bovenaan, in beide kaders en over de groepen heen. De
vraag was of dat zonder herladen kon; ja, want de ronde van vijf seconden bouwt de lijsten toch al opnieuw
op. Versie 0.5.6, alleen de pagina, dus geen nieuwe image.
Wat erbij aan het licht kwam: `lastSeen` wordt alleen bij de verbinding gezet, niet bij een sync erover.
Dat als open taak in [TAKEN.md](TAKEN.md), want het raakt de relay-code en kost dus een image.
## 04-09-2026 - de gloed uit in beide apps, als schakelaar
Geen gepland werk aan dit plan; de opdracht kwam bij Electrum Gate binnen en geldt voor beide pagina's. De
accentgloed uit 0.5.2 gaat uit, en wel zo dat hij optioneel blijft: `data-glow="on"` op het html-element
zet hem terug, en alleen samen met `data-depth="deep"`. Die combinatie-eis is van de gebruiker en hij is
juist: een gloed achter dekkende vlakken is geen stand die iemand bedoelt.
De blur hangt nu aan de gloed en niet aan de diepte, waardoor het mobiele terugvalblok uit 0.5.3 verviel:
dat zette drie dingen om die al uit stonden. Versie 0.5.5, alleen de pagina, dus geen nieuwe image. Voor de
onderbouwing en de meegevonden specificiteitsfout: **Webinterface**, `PROGRESS.md` van dezelfde dag.
## 30-08-2026 - de pagina op de lijst van de gebruiker, plus twee onderzoeksvragen
De lijst waar taak "statuspagina verbeteren" op wachtte, kwam er: negen punten uit echt gebruik. Alle negen
in één ronde gedaan, als 0.5.0. Het grootste stuk is het **tijdvenster van twee minuten** voor nieuwe
eigenaars, en de bepalende keuze daarin is dat de teller in het relay-proces zit en niet in de pagina: een
teller in een tabblad dat je sluit, sluit de deur niet. Verder labels op een eigenaar-id, de twee lijsten met
buitengesloten eigenaars samengevoegd, en de pagina qua maatvoering gelijkgetrokken met Electrum Gate. Beide
apps hebben nu een menu-item "About this app".
**De twee onderzoeksvragen zijn beantwoord en één premisse bleek fout.** Een geweigerde eigenaar komt níet in
de database: `isOwnerAllowed` zit in de WebSocket-upgrade, dus het is een 401 en een gesloten socket. Wat de
gebruiker wilde gebeurt alsnog, maar via de cliënt: die is local-first en levert bij toelating de hele
geschiedenis. En de blobs zijn niet met een xpub te ontcijferen, want een `OwnerId` wordt niet uit een xpub
gemaakt; daar is de SLIP-21-node van het apparaat voor nodig, en die geeft volledige zeggenschap. Dat hoort
dus in de Trezor-tool en niet hier.
**Wat deze ronde onverwacht opleverde was gereedschap.** Een `*.template` is niet in een browser te openen,
dus is er nu `tools/voorbeeldpagina.mjs` die er een bekijkbare versie van maakt met nagemaakte gegevens. Dat
vond meteen twee echte fouten die ik anders op het apparaat had gevonden: de badges stonden in het hover-blok
en waren dus onzichtbaar, en de onzichtbare knoppen namen wél ruimte in de kop waardoor er een kop over twee
regels brak. Daarnaast drie nieuwe toetsen, waaronder de dollarteken-toets waarvan het commentaar in drie
bestanden al beweerde dat hij bestond.
**De image is dezelfde dag gebouwd en geduwd**, op de Umbrel want daar staat Docker, en gepind op
`sha256:1e4dafdc…acea49a`. Twee dingen die het opleverde en die bij de volgende verhoging weer gelden: laat
de digest er even ván af zolang de image niet bestaat, want een oude digest onder een nieuwe tag levert stil
de oude image, en doe **beide** repo-pushes vóór de app-update, anders kost het toevoegen van de digest een
extra versienummer. `imagetools` bevestigde bovendien `manifest.v2+json` en dus één architectuur; dat is bij
bouwen op het apparaat zelf te verwachten.
**En daarna kwam de eerste melding uit echt gebruik binnen, binnen het uur: 0.5.1.** De melding bovenaan de
pagina ging nooit weg. Eén ontbrekende tak in de ronde, die hem wel aanzette maar nooit uit; "Sent." bleef
dus staan tot een herlading. Het is het soort fout dat de voorbeeldweergave niet vindt, want daar klik je
één keer en kijk je naar de opmaak. Wat het wél oplevert is een scherpere regel: de melding hoort van de
**ronde** te zijn en niet van de klik, want alleen de ronde weet of hij nog waar is. Een weigering is de
uitzondering en blijft staan, want dat is de enige uitleg die je krijgt.
Bijvangst: die weigering stond in de neutrale stijl en is nu rood. En het manifest loopt hiermee vóór op de
image (0.5.1 tegen 0.5.0), wat correct is voor een wijziging in alleen een template. De changelog beweerde
sinds diezelfde ochtend het tegenovergestelde en is rechtgezet.
**Bij het afsluiten haalde de gebruiker een taak van de lijst in plaats van erbij.** De suite meldt bij elke
ronde dat `python:3-alpine` en `nginx:alpine` niet gepind zijn, en de verleiding is dat "even te doen". Zijn
punt: pinnen is een eis van **Publicatie**, maar daarvóór gaat de app-inhoud eerst een eigen image in, en dan
bestaan die twee niet meer als de plek waar onze code draait. Dus eerst **Eigenimage**.
Dat klopt, en het bleek breder dan de taak: §8 van dat plan gokte er nog op dat het bouwrecept van deze app
zou vervallen zodra de kale relay de weg werd, en dat is ingehaald. Wij bouwen juist een eigen image, want de
twee terugroepfuncties zitten niet in de gepubliceerde. Bovendien staat ook bij Evolu Relay de agent én de
pagina nog op vreemde images; alleen de relay zelf is klaar. Beide rechtgezet, en de volgorde-regel staat nu
als cross-plan kennis in `KNOWLEDGE.md`, want vier plannen raken hem en geen van de vier bezit hem.
**Wat er nu nog staat is van de gebruiker en is een blik, geen sessie:** verversen, updaten, en dan de drie
dingen nakijken die alleen het apparaat kan zeggen.
## 28-08-2026 (slot) - de app draait en wordt gebruikt; iOS doet niet mee
Het pakket is af en geïnstalleerd als 0.3.0. De Mac synchroniseert eroverheen, over TLS via Zoraxy op een
eigen subdomein; de handshake-curl geeft daar `101 Switching Protocols`. De allowlist werkt in beide
richtingen: een eigenaar geleerd, de deur gesloten, en een tweede eigenaar die daarna aanklopte alsnog
toegelaten met de knop op de pagina.
**Twee dingen die uit echt gebruik kwamen en die geen nadenken had opgeleverd.** Eén cliënt kan meer dan
één eigenaar hebben, want een `OwnerId` hoort bij een wallet en niet bij een apparaat; "de eerste eigenaar
wint" sloot daarmee de tweede wallet van de gebruiker buiten. En de korte containernaam `agent` is op het
gedeelde Docker-netwerk niet uniek, waardoor de helft van de verzoeken bij Electrum Gate uitkwam en die app
kapotging. Beide zijn gerepareerd, de tweede met een toets erop.
**iOS is aan het eind van de dag alsnog verklaard, en het ligt niet aan dit pakket.** TLS was niet de
oorzaak: ook met certificaat, opgeslagen URL en de sync-schakelaar aan kwam er niets aan, en sinds 0.3.0 is
dat hard gemeten omdat de relay élke eigenaar logt. De verklaring kwam uit de cliëntcode plus een opmerking
van de gebruiker: de `OwnerId` wordt op de Trezor afgeleid, en zijn model kan nog niet aan een iPhone. Zonder
apparaat kent de app geen device state, en dan slaat Suite een label lokaal op en doet verder niets, zonder
melding. Bronregels in [OPEN.md](OPEN.md) punt 9.
**En de leeskant is aan het eind van de dag alsnog bewezen**, met een tweede gebruikersaccount op dezelfde
Mac. Dat is zelfs schoner dan een tweede apparaat: die installatie begint met een lege lokale
Evolu-database, dus alles wat daar verschijnt kan alleen van de relay komen. De labels kwamen door.
**Daarmee is het doel van dit plan gehaald:** een tweede app in deze store, geïnstalleerd op de Umbrel, met
Trezor Suite die erop synchroniseert, heen en terug. Wat er nog openstaat is klein: een herstart van de app
overleven, twee images pinnen, en het wissen van data per eigenaar.
**Geraakt:** de hele app-map, `tools/evolu-relay/`, `tests/`, `CLAUDE.md`, `whatsnext-electrum-gate/` en de
documentatie. **Tests:** alle vier groen.
## 28-08-2026 (avond) - het pakket is omgebouwd: relay, agent en pagina
De app-map is nu de kale relay met onze allowlist eromheen. De poorten zijn omgedraaid: de statuspagina
hangt achter `app_proxy` mét de inlog van umbrelOS, de relay publiceert 3852 waar Zoraxy met TLS naartoe
gaat wijzen. Postgres, het wachtwoord en de quota-manager zijn eruit.
De gebruiker heeft de image gebouwd en naar het Gitea-register geduwd; de compose is gepind op tag plus
digest. Manifest naar 0.2.0, met een beschrijving en releaseNotes die niet langer over Trezor's relay en
een quota-manager gaan.
**Het relay-programma is daarna wél gemeten**, losgedraaid uit het register en buiten de app om: het log
meldde `learned a new owner`, en in `/app/data` stonden `evolu-relay.db` van 49152 bytes en `owners.json`
van 241 bytes. De hele keten werkt dus. Wat nog ongetest is, is de pagina en de agent: alleen op syntaxis
gecontroleerd, nooit gerenderd, en of een knop bij het relay-proces aankomt is niet gemeten.
Terzijde, want het leek een storing: Suite meldt bij een nieuwe migratie "0 labels migrated successfully,
11 skipped". Dat is geen fout. Uit hun eigen teksten: alleen ontbrekende labels worden gekopieerd, en die
elf stonden al in de lokale Evolu-database.
**Geraakt:** de hele app-map, `tools/evolu-relay/build.sh` en de documentatie van dit plan.
**Tests:** alle vier groen (32, 54, 39 en 60 goed, 0 fout).
## 28-08-2026 (later) - het relay-programma staat, met een test erop
`tools/evolu-relay/src/` bevat nu een eigen programma dat `createRelay` uit `@evolu/nodejs` aanroept met
onze twee terugroepfuncties. Het beleid staat apart in `policy.js` als pure functies, zodat het te toetsen
is zonder relay en zonder Docker: `node tests/test_limiter.mjs`, 60 toetsen. De beslissende regel is
muteertest gedaan, en de juiste toets viel om: een geblokkeerde eigenaar mag er niet alsnog in via de
leerstand.
`build.sh` bouwt niet langer de repo van Trezor maar onze eigen Dockerfile, en Git is daarvoor niet meer
nodig. Onderweg bleek dat de 1 MB uit de gepubliceerde image **per schrijfactie** geldt en niet per
eigenaar; dat is op twee plekken rechtgezet waar het verkeerd stond.
**Geraakt:** `tools/evolu-relay/`, `tests/test_limiter.mjs`, `CLAUDE.md` en de documentatie van dit plan.
**Tests:** alle vier groen (32, 54, 39 en 60 goed, 0 fout).
## 28-08-2026 - de proef is geslaagd: de kale relay werkt
**Trezor Suite op de desktop stuurde elf labels naar `docker.io/evoluhq/relay:latest` en die kwamen aan**:
de database in het volume groeide van 40960 naar 49152 bytes. Daarmee klopt de protocolversie en is de
richting van 27-08 een gemeten feit. Geen Postgres, geen quota-manager, geen eigenaarsregistratie. De
leeskant is niet bewezen: er is geen tweede cliënt geweest.
Het testprotocol staat nu uitgeschreven in [PLAN.md](PLAN.md) §6a, en het is tijdens de sessie drie keer
gecorrigeerd op instrumenten die een **vals negatief** gaven: `docker diff` kijkt niet in een volume, een
browser of gewone `curl` krijgt van deze WS-server niets terug, en `healthy` komt uit een healthcheck die
alleen een TCP-verbinding opent. Alle drie wezen ze een probleem aan dat er niet was.
**iOS deed niet mee en dat is een eigen vraag geworden** (open punt 9). Het veld voor de relay-URL bestaat
daar wel, valideert op `http(s)://` en onthoudt zijn waarde, maar de app doet geen enkele
verbindingspoging. Het vermoeden is TLS; dat is niet gemeten en het is duur, want een certificaat voor een
LAN-adres bestaat niet.
**Het ontwerp van de limiter en de pagina staat er ook**, en de limiter bleek veel kleiner dan
**Bereikbaarheid** §4b aannam: `createRelay` neemt twee terugroepfuncties, en Trezor doet zelf niets
anders. De poorten gaan om, zodat de pagina achter de umbrelOS-inlog kan en Zoraxy met TLS naar de relay
wijst. De gebruiker heeft de oude installatie weggehaald; die wordt niet geüpdatet maar opnieuw gebouwd.
**Geraakt:** documentatie van dit plan plus `Referenties/Upstream-evolu-relay.md` §8. **Tests:** niet van
toepassing.
## 27-08-2026 - richting gekozen: kaal, met een limiter en een pagina erop
Nog niets gebouwd; dit is een sessie waarin alleen vastgelegd is. De gebruiker kiest voor de kale
Evolu-relay, met een eigen limiter erop en de statuspagina uit open punt 2. Zijn woorden: "dat wordt beter."
De app staat op dit moment uit op de Umbrel.
**Die drie horen bij elkaar en dat is het inzicht van vandaag.** De kale relay heeft geen toegangscontrole,
en dat is bij Evolu het ontwerp en geen omissie. Wat vandaag de schade beperkt is juist een
applicatiecontrole: Trezor's relay weigert elke eigenaar zonder limietenrij. Kaal gaan haalt precies die
bescherming weg, dus de limiter is de vervanging ervan en geen extraatje. En de statuspagina krijgt er een
taak bij: tonen wélke eigenaar toegelaten is, met de knop om die keuze te wissen.
**De spanning is opgeschreven in plaats van weggepoetst:** de meest voor de hand liggende limiter breidt de
relay uit via `createRelay`, en dan komen de bouwstap en het eigen register terug die kaal gaan juist zou
besparen. De gebruiker heeft dat ondervangen: de broncode staat nog op zijn Umbrel, dus opnieuw bouwen en
naar het eigen Gitea-register duwen kan gewoon. Wat sowieso vervalt zijn de Postgres en het wachtwoord.
**Het blijft een richting en geen besluit**, want de proef is niet gedaan: klopt de protocolversie van
`docker.io/evoluhq/relay:latest` met de `@evolu/web@3.0.0-next.1` plus patch die Suite gebruikt? Die proef
blijft de eerste taak, en er wordt niets verbouwd voordat hij geslaagd is.
**Geraakt:** alleen documentatie van dit plan. **Tests:** niet van toepassing.
## 25-08-2026 - hij draait, en de weg erheen liep via een verkeerde aanname
Gebouwd op de Umbrel in 50 seconden, en toen twee keer misgegaan op dezelfde muur voordat die muur
begrepen was.
**De installatie faalde met `pull access denied` op een image die lokaal gebouwd was.** Mijn eerste
verklaring kwam uit `app-script` van umbreld, waar `install` een `compose "${app}" pull` doet, en de
reparatie was `pull_policy: never`. Dat faalde identiek. De oorzaak stond in de stacktrace en niet in de
foutmelding: `at /opt/umbreld/node_modules/docker-modem/lib/modem.js:382`. **umbreld haalt élke image via
de Docker Engine API op en niet via compose**, dus `pull_policy` wordt nooit gelezen. `app-script` is de
legacy-compat-laag en niet het pad dat umbrelOS 1.x loopt. Les die breder is dan deze app: een bron die
overtuigend leest, is nog geen bron die het draaiende pad beschrijft.
**Daarmee was "lokaal bouwen als eerste stap" geen keuze meer.** Elke image moet anoniem uit een register
komen. Dat werd het Gitea-register op dezelfde server als de store: `sc.kamenier-hamer.nl/sysop/evolu-relay`,
gepind op tag plus digest. Anoniem halen is eerst met een uitgelogde pull gecontroleerd, met dezelfde
valkuil als bij het klonen van de store (uitloggen in de context waarin je inlogde, anders meet je je eigen
sessie).
**Daarna draaide hij in één keer.** Drie containers omhoog, `db` gezond, de app-proxy op 3851. De lokale
images waren vooraf weggehaald, dus de pull is echt gedaan en het hele pad is bewezen: broncode van Trezor →
bouwrecept → eigen register → gepinde digest → installatie.
Kleinigheid met een staart: de gebruiker `umbrel` zit hier niet in de groep `docker`, dus bouwen vraagt
`sudo`. Dat komt bij elke herbouw terug en staat daarom in het plan.
**Geraakt:** `whatsnext-evolu-relay/docker-compose.yml` en `umbrel-app.yml` (0.0.2),
`tools/evolu-relay/build.sh`, `Docs/Referenties/Umbrel-appstore-spec.md`, dit plan.
**Tests:** 32 goed 0 fout, 54 goed 0 fout en 39 goed 0 fout, niets overgeslagen.
**Nagekomen dezelfde dag, op de vraag van de gebruiker of deze app kwaad kan: er is een tweede weg naar
binnen die in geen enkel bestand van deze app staat.** umbrelOS maakt per app een Tor hidden service, en
die stond aan. In combinatie met `PROXY_AUTH_ADD: "false"` betekent dat: de relay bereikbaar vanaf het
internet, alleen beschermd doordat het `.onion`-adres onraadbaar is. De gebruiker zet hem uit.
Het mechanisme geldt voor élke app en staat daarom in de gedeelde naslag en niet alleen hier. Het gevólg
verschilt: bij Electrum Gate legt dezelfde hidden service alleen de inlogpagina van umbrelOS bloot, want
daar staat de proxy-inlog gewoon aan. De les is de combinatie en niet de instelling: wie
`PROXY_AUTH_ADD` op `"false"` zet, moet meteen de hidden service beoordelen.
**Wat nog steeds niet bewezen is:** of het databaseschema zichzelf aanmaakt, of Trezor Suite dit adres
accepteert, en of een eigenaar te registreren is. "Draait" is hier nog geen "synchroniseert".
## 25-08-2026 - plan werd actief, en het pakket staat er
De gebruiker besloot het pakket meteen te maken in plaats van te wachten op de proefopstelling. Dat kon,
omdat fase 1 van **Proefopstelling** die middag de feiten had opgeleverd: één image met meerdere
startscripts, een quota-manager die in de praktijk verplicht is, en geen publieke image.
**De zwaarste ontwerpvraag is met een precedent beslecht en niet met een gok.** Trezor Suite kan niet achter
de inlog van umbrelOS, en het was onduidelijk of `PROXY_AUTH_ADD: "false"` dan een verantwoorde keuze is of
een omweg. De eigen `nostr-relay`-app van Umbrel doet exact hetzelfde, om precies dezelfde reden, en heeft
ook geen eigen `ports:`. Daarmee is de vorm van de compose niet zelfbedacht.
**Twee dingen kwamen boven die niet in het ontwerp zaten.** `build/` staat in `.gitignore` als bouwselmap,
dus het bouwrecept zou stilzwijgend buiten de repo zijn gebleven; het staat nu onder `tools/`. Dat kwam pas
bij het stagen aan het licht en niet bij het schrijven, wat precies is waarom `git status` lezen erbij hoort.
En de poort werd 3851 en niet 4000: 4000 is de eigen poort van de relay, maar ook een veelgebruikte poort,
en een botsing op de host merk je pas als de app niet start. Die les komt van 50002 tegen Fulcrum.
**Er is een derde testbestand**, `tests/test_appstore_vorm.py`, en het gaat over de store en niet over één
app: id gelijk aan mapnaam, store-voorvoegsel, veldvolgorde, `app_proxy` die naar een bestaande service
wijst, en elke gemounte map die in de repo bestaat. Het vindt zijn apps zelf, dus een derde app valt er
automatisch onder. Digests toetst het expres niet: geen van de twee apps haalt die regel vandaag, en een
suite die altijd rood staat wordt niet gelezen.
Mutatie-getest met drie ingrepen: het app-id laten afwijken van de mapnaam, `APP_HOST` naar een
niet-bestaande service laten wijzen, en de `.gitkeep` weghalen. Alle drie vielen om bij de juiste toets, met
een bruikbare melding, en `git diff` was daarna leeg.
**Geraakt:** `whatsnext-evolu-relay/` (nieuw), `tools/evolu-relay/build.sh` (nieuw),
`tests/test_appstore_vorm.py` (nieuw), `Docs/CHANGELOG-evolu-relay.md` (nieuw), dit plan,
`Docs/CONTINUE_HERE.md`, `CLAUDE.md`, `README.md`.
**Tests:** 32 goed 0 fout (nieuw), 39 goed 0 fout en 54 goed 0 fout (bestaand), niets overgeslagen.
**Wat er níet geverifieerd is, en dat is veel:** er is nog niets gebouwd en niets geïnstalleerd. Of het
databaseschema zichzelf aanmaakt, of Trezor Suite dit adres accepteert, en of een eigenaar te registreren is
zonder iets extern: alle drie onbekend. "Gebouwd" is hier nadrukkelijk niet "werkend".