diff --git a/.claude/rules/website.md b/.claude/rules/website.md index db76c350..c34cee8d 100644 --- a/.claude/rules/website.md +++ b/.claude/rules/website.md @@ -43,8 +43,9 @@ to the visible answers, and nothing after ``. ## The dashboard pictures -`website/assets/dashboard--hu.webp` / `-en.webp` show **controller 0.303.0** (taken 2026-10-08 on demo-hp's -household guest; method and privacy scan: `documentation/audits/website-dashboard-2026-10-08/`). **A controller +`website/assets/dashboard--hu.webp` / `-en.webp` show **controller 0.305.0–0.307.0** (retaken 2026-10-10 on +demo-hp's household guest; method and privacy scan: `documentation/audits/register-shrink-2026-10-10/r910/`, first +taken by `documentation/audits/website-dashboard-2026-10-08/`). **A controller release that visibly changes a pictured page (Launcher, Apps, an app's page, Backup → Apps) should refresh them.** Every caption is a claim (`captions-claims.md` there). Gate 19 keeps each page on its own language's pictures. diff --git a/documentation/architecture/01-topology-and-trust.md b/documentation/architecture/01-topology-and-trust.md index 50975275..25055d72 100644 --- a/documentation/architecture/01-topology-and-trust.md +++ b/documentation/architecture/01-topology-and-trust.md @@ -201,7 +201,10 @@ app. Controller down → the gated app answers an error, never the app. Measured - **Cloudflare Tunnel** provides inbound access to apps and the controller UI (the CGNAT solution). Tunnel token lives in the hub record → **reused on new hardware during DR**, so - DNS/routing stay intact through an outage. + DNS/routing stay intact through an outage. **TLS ends at Cloudflare's edge**: the tunnel's public end is + Cloudflare, so Cloudflare can technically read a household's app and dashboard traffic (the FAQ says so since + 2026-10-08, R-900). Replacing it with our own relay is deferred until after the first customers (R-904, `09` §3 + decision 184). - **Outbound only** for control/report/backup (poll to hub, push to PBS). No inbound control endpoint exists in the chosen model. - **Every customer has their OWN domain — never a name under `felhom.eu`** (operator ruling diff --git a/documentation/audits/register-shrink-2026-10-10/databases-table-readback-0.307.0.txt b/documentation/audits/register-shrink-2026-10-10/databases-table-readback-0.307.0.txt new file mode 100644 index 00000000..47f3db9f --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/databases-table-readback-0.307.0.txt @@ -0,0 +1,6 @@ +Backup -> Apps "Databases" table, read 2026-10-10 ~13:26Z through GET /backups/apps (the page the UI renders), controller 0.307.0, +right after the delivery restart (so the run is SYNTHESISED from the files on disk — the case the fix changed): +9201 rows 6 dupes {} apps 6 (demo-hp household guest) +tester-1 rows 2 dupes {} apps 2 +Before (control, same box, controller 0.305.0 after its 07:59Z restart): r910/backups-hu-0.305.0-duplicates.png — +adventurelog x4, bookstack x4 (the pre-restore undo copies on hdd_1/backups/secondary//recovery-unit/db-dumps/). diff --git a/documentation/audits/register-shrink-2026-10-10/delivered-0.307.0.txt b/documentation/audits/register-shrink-2026-10-10/delivered-0.307.0.txt new file mode 100644 index 00000000..a9e033a3 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/delivered-0.307.0.txt @@ -0,0 +1,12 @@ +== demo-hp +Oct 10 15:22:49 demo-hp felhom-priv-apply[1964944]: felhom-priv-apply: WROTE controller-image 9201 gitea.dooplex.hu/admin/felhom-controller:0.307.0 +Oct 10 15:22:56 demo-hp felhom-agent[1262]: time=2026-10-10T15:22:56.722+02:00 level=INFO msg="controller-swap: new controller healthy" vmid=9201 target=gitea.dooplex.hu/admin/felhom-controller:0.307.0 +2026/10/10 13:22:52 main.go:341: [INFO] felhom-controller 0.307.0 starting (customer: demo-hp, domain: enkisfelhom.hu) +== felhom-pve +Oct 10 15:22:47 demo-felhom felhom-priv-apply[573740]: felhom-priv-apply: WROTE controller-image 9201 gitea.dooplex.hu/admin/felhom-controller:0.307.0 +Oct 10 15:22:57 demo-felhom felhom-agent[1205]: time=2026-10-10T15:22:57.155+02:00 level=INFO msg="controller-swap: new controller healthy" vmid=9201 target=gitea.dooplex.hu/admin/felhom-controller:0.307.0 +2026/10/10 13:22:49 [INFO] felhom-controller 0.307.0 starting (customer: demo-felhom, domain: demo-felhom.eu) +== root@192.168.0.154 +Oct 10 15:22:50 felhom felhom-priv-apply[845246]: felhom-priv-apply: WROTE controller-image 9201 gitea.dooplex.hu/admin/felhom-controller:0.307.0 +Oct 10 15:22:57 felhom felhom-agent[1161]: time=2026-10-10T15:22:57.403+02:00 level=INFO msg="controller-swap: new controller healthy" vmid=9201 target=gitea.dooplex.hu/admin/felhom-controller:0.307.0 +2026/10/10 13:22:52 [INFO] felhom-controller 0.307.0 starting (customer: tester-1, domain: enkicsifelhom.hu) diff --git a/documentation/audits/register-shrink-2026-10-10/floors-0.307.0.txt b/documentation/audits/register-shrink-2026-10-10/floors-0.307.0.txt new file mode 100644 index 00000000..21fc2c53 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/floors-0.307.0.txt @@ -0,0 +1,7 @@ +== floors 2026-10-10T13:22:39Z: 0.307.0 with min_agent 0.131.0; global floor not touched; Tester 2 not touched +demo-hp: HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set +demo-felhom: HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set +tester-1: HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set +2026/10/10 15:22:39 [INFO] Customer demo-hp controller-version floor override set to "0.307.0" (declared MinAgent "0.131.0") +2026/10/10 15:22:39 [INFO] Customer demo-felhom controller-version floor override set to "0.307.0" (declared MinAgent "0.131.0") +2026/10/10 15:22:39 [INFO] Customer tester-1 controller-version floor override set to "0.307.0" (declared MinAgent "0.131.0") diff --git a/documentation/audits/register-shrink-2026-10-10/r815-gc-tasks.txt b/documentation/audits/register-shrink-2026-10-10/r815-gc-tasks.txt new file mode 100644 index 00000000..23f628ea --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r815-gc-tasks.txt @@ -0,0 +1,14 @@ +00 +01 +UPID:felhom-hetzner:000003FD:0000054D:00000002:6A6792AB:garbage_collection:felhom\x2doffsite:root@pam: 6A6792D3 OK +UPID:felhom-hetzner:000048A2:001E9035:00000019:6A683397:garbage_collection:felhom\x2doffsite:root@pam: 6A6833C1 OK +UPID:felhom-hetzner:00014E5D:00468C0B:0000006B:6A6EC7C8:garbage_collection:felhom\x2doffsite:root@pam: 6A6EC7F5 OK +UPID:felhom-hetzner:00000495:000006CA:0000008C:6A780248:garbage_collection:felhom\x2doffsite:root@pam: 6A780274 OK +UPID:felhom-hetzner:00000495:000006CA:000000E5:6A813CC8:garbage_collection:felhom\x2doffsite:root@pam: 6A813CF4 OK +UPID:felhom-hetzner:00086AE7:07B20B30:00000045:6A8A7748:garbage_collection:felhom\x2doffsite:root@pam: 6A8A7777 OK +UPID:felhom-hetzner:00086AE7:07B20B30:000000AB:6A93B1C8:garbage_collection:felhom\x2doffsite:root@pam: 6A93B1F5 OK +UPID:felhom-hetzner:00086AE7:07B20B30:00000105:6A9CEC48:garbage_collection:felhom\x2doffsite:root@pam: 6A9CEC77 OK +UPID:felhom-hetzner:00086AE7:07B20B30:0000015F:6AA626C8:garbage_collection:felhom\x2doffsite:root@pam: 6AA626F2 OK +UPID:felhom-hetzner:00086AE7:07B20B30:000001C3:6AAF6148:garbage_collection:felhom\x2doffsite:root@pam: 6AAF6175 OK +UPID:felhom-hetzner:00086AE7:07B20B30:0000022C:6AB89BC8:garbage_collection:felhom\x2doffsite:root@pam: 6AB89BF1 OK +UPID:felhom-hetzner:00086AE7:07B20B30:000002A6:6AC1D648:garbage_collection:felhom\x2doffsite:root@pam: 6AC1D674 OK diff --git a/documentation/audits/register-shrink-2026-10-10/r899-press-tester1.txt b/documentation/audits/register-shrink-2026-10-10/r899-press-tester1.txt new file mode 100644 index 00000000..385d08f8 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r899-press-tester1.txt @@ -0,0 +1,2 @@ +status before ('200', {'data': {'backup': {'target_id': 'local', 'vmid': 9201, 'archive': 'local:backup/vzdump-lxc-9201-2026_10_10-04_51_24.tar.zst', 'mode': 'snapshot', 'crash_consistent': True, 'size_bytes': 1570236783, 'success': True, 'started_at': '2026-10-10T02:51:23Z', 'duration_seconds': 86.812856699}, 'error': '', 'job_id': 'backup-9201-1791600683739394721', 'phase': 'done'}, 'ok': True}) +trigger ('200', {'data': {'started': True}, 'ok': True}) diff --git a/documentation/audits/register-shrink-2026-10-10/r903-check.js b/documentation/audits/register-shrink-2026-10-10/r903-check.js new file mode 100644 index 00000000..06f8423b --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r903-check.js @@ -0,0 +1,23 @@ +// R-903 check: a phone (376 px), JavaScript OFF — can the menu links and the globe be reached? +const puppeteer = require('puppeteer'); const http = require('http'); const fs = require('fs'); const path = require('path'); +const root = process.argv[2], port = 8099; +const types = {'.html':'text/html; charset=utf-8','.css':'text/css','.svg':'image/svg+xml','.png':'image/png'}; +const srv = http.createServer((q, r) => { let p = decodeURIComponent(q.url.split('?')[0]); if (p.endsWith('/')) p += 'index.html'; + const f = path.join(root, p); fs.readFile(f, (e, d) => { if (e) { r.writeHead(404); return r.end(); } r.writeHead(200, {'Content-Type': types[path.extname(f)] || 'application/octet-stream'}); r.end(d); }); }); +srv.listen(port, async () => { + const b = await puppeteer.launch({args: ['--no-sandbox']}); + for (const pg of ['/', '/en/']) { + const p = await b.newPage(); await p.setJavaScriptEnabled(false); + await p.setViewport({width: 376, height: 812, deviceScaleFactor: 2, isMobile: true, hasTouch: true}); + await p.goto(`http://127.0.0.1:${port}${pg}`, {waitUntil: 'load'}); + const res = await p.evaluate(() => Array.from(document.querySelectorAll('.nav-links > li > a, .nav-links .lang-globe-btn')).map(a => { + const r = a.getBoundingClientRect(); const cs = getComputedStyle(a); + const ok = r.width > 0 && r.left >= 0 && r.right <= window.innerWidth && r.top >= 0 && r.bottom <= window.innerHeight && cs.visibility !== 'hidden'; + return [a.textContent.trim() || 'globe', Math.round(r.left), Math.round(r.right), Math.round(r.top), ok]; })); + const reach = res.filter(x => x[4]).length; + console.log(pg, `reachable ${reach}/${res.length}`, JSON.stringify(res)); + await p.screenshot({path: `/out/${pg === '/' ? 'hu' : 'en'}-376-nojs.png`}); + await p.close(); + } + await b.close(); srv.close(); +}); diff --git a/documentation/audits/register-shrink-2026-10-10/r903-phone-nojs.txt b/documentation/audits/register-shrink-2026-10-10/r903-phone-nojs.txt new file mode 100644 index 00000000..3b80697e --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r903-phone-nojs.txt @@ -0,0 +1,10 @@ +== old +/ reachable 0/7 [["Szolgáltatások",409,624,80,false],["Alkalmazások",409,624,140,false],["Technológiák",409,624,200,false],["Mentések",409,624,260,false],["GYIK",409,624,320,false],["Kapcsolat",409,624,379,false],["globe",409,431,439,false]] +/en/ reachable 0/7 [["Services",409,624,80,false],["Apps",409,624,140,false],["Technology",409,624,200,false],["Backups",409,624,260,false],["FAQ",409,624,320,false],["Contact",409,624,379,false],["globe",409,431,439,false]] +== new +/ reachable 7/7 [["Szolgáltatások",24,128,76,true],["Alkalmazások",148,247,76,true],["Technológiák",24,117,118,true],["Mentések",137,207,118,true],["GYIK",227,265,118,true],["Kapcsolat",24,94,159,true],["globe",114,136,159,true]] +/en/ reachable 7/7 [["Services",24,85,76,true],["Apps",105,142,76,true],["Technology",162,243,76,true],["Backups",263,324,76,true],["FAQ",24,56,118,true],["Contact",76,131,118,true],["globe",151,173,118,true]] +== new, JavaScript ON (unchanged: the closed off-canvas menu, as before) +== new-JS-on/ reachable 0/7 [["Szolgltatsok",409,624,80,false],["Alkalmazsok",409,624,140,false],["Technolk",409,624,200 + +(the screenshots were lost: the archive was mixed with the script's own output and the guest folder was removed before a second copy; the numbers above are the measurement) diff --git a/documentation/audits/register-shrink-2026-10-10/r907-launcher-390.png b/documentation/audits/register-shrink-2026-10-10/r907-launcher-390.png new file mode 100644 index 00000000..cfd03222 Binary files /dev/null and b/documentation/audits/register-shrink-2026-10-10/r907-launcher-390.png differ diff --git a/documentation/audits/register-shrink-2026-10-10/r907-r909-demo-hp-readback.txt b/documentation/audits/register-shrink-2026-10-10/r907-r909-demo-hp-readback.txt new file mode 100644 index 00000000..72ea6072 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r907-r909-demo-hp-readback.txt @@ -0,0 +1,3 @@ +R-907 phone /launcher {"version":"0.305.0","layoutWidth":390,"screen":390,"button":[16,192],"buttonText":"Share the launcher"} +R-909 /stacks @1440 {"cards":61,"nameUnderATag":0,"logoOffsets":[0],"addressOnTwoLines":0,"layoutWidth":1440,"screen":1440} +R-909 /stacks @390 {"cards":61,"nameUnderATag":0,"logoOffsets":[0],"addressOnTwoLines":0,"layoutWidth":390,"screen":390} diff --git a/documentation/audits/register-shrink-2026-10-10/r907-r909-measure.js b/documentation/audits/register-shrink-2026-10-10/r907-r909-measure.js new file mode 100644 index 00000000..4b60d205 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r907-r909-measure.js @@ -0,0 +1,31 @@ +// R-907 / R-909 read-back on demo-hp's household guest 9201 (controller 0.305.0), read only, Hungarian, no clicks. +const puppeteer = require('puppeteer'); const fs = require('fs'); +const HOST = 'felhom.enkisfelhom.hu', BASE = 'https://' + HOST; +const sess = fs.readFileSync('/in/session.txt', 'utf8').trim(); const [k, v] = sess.split('='); +(async () => { + const b = await puppeteer.launch({args: ['--no-sandbox', '--ignore-certificate-errors', `--host-resolver-rules=MAP * ${process.env.TRAEFIK}`]}); + const p = await b.newPage(); + await p.setCookie({name: k, value: v, domain: HOST, path: '/', secure: true, httpOnly: true}); + await p.setViewport({width: 390, height: 844, deviceScaleFactor: 2, isMobile: true, hasTouch: true}); + await p.goto(BASE + '/launcher', {waitUntil: 'networkidle2'}); + const r907 = await p.evaluate(() => { const btn = document.querySelector('.share-open-btn'); const r = btn && btn.getBoundingClientRect(); + return {version: (document.body.innerText.match(/0\.30\d\.\d/) || [''])[0], layoutWidth: document.documentElement.scrollWidth, screen: window.innerWidth, + button: r ? [Math.round(r.left), Math.round(r.right)] : null, buttonText: btn ? btn.innerText.trim() : null}; }); + console.log('R-907 phone /launcher', JSON.stringify(r907)); + await p.screenshot({path: '/out/r907-launcher-390.png'}); + for (const w of [1440, 390]) { + await p.setViewport({width: w, height: 900, deviceScaleFactor: 1, isMobile: w < 500}); + await p.goto(BASE + '/stacks', {waitUntil: 'networkidle2'}); + const r909 = await p.evaluate(() => { const cards = Array.from(document.querySelectorAll('.stack-detail-card')); + let covered = 0, offsets = new Set(), addrTwoLines = 0, n = 0; + for (const c of cards) { const h = c.querySelector('h3'); if (!h) continue; n++; const hr = h.getBoundingClientRect(); + for (const t of c.querySelectorAll('.stack-tags > *')) { const tr = t.getBoundingClientRect(); + if (tr.width && !(tr.right <= hr.left || tr.left >= hr.right || tr.bottom <= hr.top || tr.top >= hr.bottom)) { covered++; break; } } + const logo = c.querySelector('.stack-logo-lg'); if (logo) offsets.add(Math.round(logo.getBoundingClientRect().top - hr.top)); + const a = c.querySelector('.subdomain-link'); if (a) { const ar = a.getBoundingClientRect(); if (ar.height > 2 * parseFloat(getComputedStyle(a).lineHeight || 20)) addrTwoLines++; } } + return {cards: n, nameUnderATag: covered, logoOffsets: Array.from(offsets), addressOnTwoLines: addrTwoLines, layoutWidth: document.documentElement.scrollWidth, screen: window.innerWidth}; }); + console.log('R-909 /stacks @' + w, JSON.stringify(r909)); + await p.screenshot({path: `/out/r909-stacks-${w}.png`}); + } + await b.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/documentation/audits/register-shrink-2026-10-10/r909-stacks-1440.png b/documentation/audits/register-shrink-2026-10-10/r909-stacks-1440.png new file mode 100644 index 00000000..de1a40a7 Binary files /dev/null and b/documentation/audits/register-shrink-2026-10-10/r909-stacks-1440.png differ diff --git a/documentation/audits/register-shrink-2026-10-10/r910/app-en.txt b/documentation/audits/register-shrink-2026-10-10/r910/app-en.txt new file mode 100644 index 00000000..d4d21ec4 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/app-en.txt @@ -0,0 +1,73 @@ +Launcher +Dashboard +Apps +Storage +Drives +Network storage +Backup +Overview +Remote backup +Apps +Restore +Sharing +Network sharing +System monitor +Debug +SETTINGS +System +Notifications +Security and access +0.305.0 +Sign out ↗ +← Apps +Paperless-ngx +Running +Up to date +Open ↗ +Log +Export +Settings +Automatic update at 2026-09-28 04:22 — done. + +A digital filing cabinet - scanning, OCR and automatic sorting + +~500M RAM productivity Needs a hard drive Runs on Pi +Where do I put my files? + +You reach these folders in the File manager. You may need to sign in to the File manager first. + +Documents to read in ↗ +50.5 GB free + +Copy the files to be processed in here. The app reads them and then deletes them from here — this folder is temporary and is not backed up. + +What is it for? +Turn paper documents into files and keep them in order +Automatic OCR text recognition on scanned documents +Smart automatic sorting and tagging +Full text search across every document +Documents that arrive by e-mail are processed automatically +First steps +Open papir.enkisfelhom.hu in your browser +Sign in: user "admin". The password is on the Settings page, under Automatically generated values +Upload a document in the app, OR drop it into the import/paperless folder in the File manager (FileBrowser) - it is read in and processed with OCR from there. The File manager sign-in is on the File manager app page +You can upload many documents at once: they are processed one by one, and a larger batch takes a few minutes +Create tags and correspondents so new documents sort themselves +Before you start +An external hard drive is recommended to keep the documents on +At least 1 GB of free RAM (for the OCR work) +Documentation + +Official documentation ↗ + Felhom.eu + Felhom.eu +Language + + /apps/paperless-ngx + + /apps/paperless-ngx +This app is running the newest version available. + Paperless-ngx + + + \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/app-hu.txt b/documentation/audits/register-shrink-2026-10-10/r910/app-hu.txt new file mode 100644 index 00000000..7ed5eaf8 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/app-hu.txt @@ -0,0 +1,73 @@ +Indítópult +Vezérlőpult +Alkalmazások +Tárhely +Meghajtók +Hálózati tárhely +Biztonsági mentés +Áttekintés +Távoli mentés +Alkalmazások +Visszaállítás +Megosztás +Hálózati megosztás +Rendszermonitor +Hibakeresés +BEÁLLÍTÁSOK +Rendszer +Értesítések +Biztonság és hozzáférés +0.305.0 +Kijelentkezés ↗ +← Alkalmazások +Paperless-ngx +Fut +Naprakész +Megnyitás ↗ +Napló +Exportálás +Beállítások +Automatikus frissítés 2026-09-28 04:22-kor — sikeres. + +Digitális irattár - szkennelés, OCR és automatikus rendszerezés + +~500M RAM productivity HDD szükséges Pi kompatibilis +Hova tegyem a fájlokat? + +Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe. + +Beolvasandó dokumentumok ↗ +50.5 GB szabad + +Ide másold a feldolgozandó fájlokat. Az alkalmazás beolvassa, majd törli innen — ez a mappa átmeneti, és nem készül róla biztonsági mentés. + +Mire használható? +Papír dokumentumok digitalizálása és rendszerezése +Automatikus OCR szövegfelismerés szkennelt dokumentumokon +Intelligens automatikus kategorizálás és címkézés +Teljes szöveges keresés az összes dokumentumban +Email-ből érkező dokumentumok automatikus feldolgozása +Első lépések +Nyisd meg a papir.enkisfelhom.hu címet a böngészőben +Lépj be: felhasználó "admin", a jelszó a Beállítások oldalon található (Automatikusan generált értékek) +Tölts fel egy dokumentumot a webfelületen, VAGY dobd a Fájlkezelőben (FileBrowser) az import/paperless mappába — onnan automatikusan beolvassa és OCR-rel feldolgozza. A Fájlkezelő belépése a Fájlkezelő alkalmazás oldalán található +Sok dokumentumot egyszerre is feltölthetsz: egyenként dolgozza fel őket, egy nagyobb köteg néhány percig tart +Hozz létre címkéket és levelezőket az automatikus rendszerezéshez +Előfeltételek +Külső HDD ajánlott a dokumentumok tárolásához +Legalább 1 GB szabad RAM (OCR feldolgozáshoz) +Dokumentáció + +Hivatalos dokumentáció ↗ + Felhom.eu + Felhom.eu +Nyelv + + /apps/paperless-ngx + + /apps/paperless-ngx +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + Paperless-ngx + + + \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/apps-en.txt b/documentation/audits/register-shrink-2026-10-10/r910/apps-en.txt new file mode 100644 index 00000000..1abfa6d8 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/apps-en.txt @@ -0,0 +1,769 @@ +Launcher +Dashboard +Apps +Storage +Drives +Network storage +Backup +Overview +Remote backup +Apps +Restore +Sharing +Network sharing +System monitor +Debug +SETTINGS +System +Notifications +Security and access +0.305.0 +Sign out ↗ +Apps +enkisfelhom.hu +Import +↻ Refresh templates +All (61) +Running (12) +Stopped (1) +Installable (48) +ActualBudget +Not installed + +Personal finances and budgeting + +~50M +Runs on Pi +Install +Details +AdventureLog +travel.enkisfelhom.hu +↗ +Running +Up to date + +Travel journal and trip planner + +~100M +Runs on Pi +adventurelog +Up 10 hours (healthy) +adventurelog-frontend +Up 10 hours (healthy) +adventurelog-postgres +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Audiobookshelf +Not installed + +An audiobook and podcast server + +~100M +Runs on Pi +Needs a hard drive +Install +Details +BentoPDF +pdf.enkisfelhom.hu +↗ +Running +Up to date + +A privacy-first PDF toolbox + +~100M +Runs on Pi +bentopdf +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +BookStack +wiki.enkisfelhom.hu +↗ +Running +Up to date + +A simple, book-shaped wiki and documentation platform + +~150M +Runs on Pi +bookstack +Up 10 hours (healthy) +bookstack-db +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Cal.com +Not installed + +Open source appointment booking (a Calendly alternative) + +~200M +Install +Details +Calibre-Web Automated +books.enkisfelhom.hu +↗ +Running +Up to date + +An automated e-book library - conversion, metadata and a web reader + +~200M +Needs a hard drive +NVME 1TB +calibre-web +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Claper +Not installed + +Interactive presentations that bring the room in + +~100M +Runs on Pi +Install +Details +Cloudflare Tunnel +Running + +Secure internet connection — the server is reachable from outside without opening ports. + +cloudflared +Up 10 hours (healthy) +Protected system component +Restart +Details +Code-Server +Not installed + +VS Code in your browser - write code from anywhere + +~200M +Install +Details +Crafty Controller +Not installed + +A Minecraft server manager with a web interface + +~256M +Install +Details +Dawarich +Not installed + +Your own location history on a map - instead of Google Timeline + +~500M +Install +Details +Docmost +docs.enkisfelhom.hu +↗ +Running +Up to date + +A modern wiki and documentation platform (Notion-like) + +~200M +docmost +Up 10 hours (healthy) +docmost-postgres +Up 10 hours (healthy) +docmost-redis +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Emby +Not installed + +A personal media server with live TV and recording + +~512M +Needs a hard drive +Install +Details +FileBrowser +files.enkisfelhom.hu +↗ +Running + +File manager — browse the storage files from your browser. + +filebrowser +Up 10 hours (healthy) +Protected system component +Restart +Details +Ghost +Not installed + +A professional blog and newsletter platform + +~150M +Install +Details +Gitea +Not installed + +A light Git server of your own, with a web interface + +~100M +Runs on Pi +Install +Details +Glance +Not installed + +A minimalist information dashboard + +~20M +Runs on Pi +Install +Details +Gokapi +Not installed + +Temporary file sharing with links that expire + +~30M +Runs on Pi +Install +Details +Grafana +Not installed + +A professional monitoring and visualisation platform + +~100M +Runs on Pi +Install +Details +Gramps Web +Not installed + +Family tree and genealogy software + +~384M +Runs on Pi +Install +Details +Grimmory +Not installed + +An e-book library with an in-browser reader, OPDS, Kobo and KOReader sync + +~600M +Runs on Pi +Needs a hard drive +Install +Details +Grocy +Not installed + +Running the household: what is in the house, what runs out, the shopping list and the chores + +~60M +Runs on Pi +Install +Details +Home Assistant +Not installed + +An open source smart home hub + +~256M +Runs on Pi +Install +Details +Homebox +Not installed + +A home inventory for your things + +~50M +Runs on Pi +Install +Details +Homepage +Not installed + +A start page you build yourself, with service widgets + +~50M +Runs on Pi +Install +Details +Immich +Not installed + +Photos and videos in one place (a Google Photos alternative) + +~2048M +Needs a hard drive +Install +Details +Jellyfin +Not installed + +A free and open source media server + +~512M +Needs a hard drive +Install +Details +Jellyseerr +Not installed + +Media requests, wired into Jellyfin/Plex + +~100M +Runs on Pi +Install +Details +Karakeep +Not installed + +Bookmarks, articles, notes and images in one place, searchable + +~700M +Install +Details +Kimai +time.enkisfelhom.hu +↗ +Running +Up to date + +Time tracking and project management + +~100M +Runs on Pi +kimai +Up 10 hours (healthy) +kimai-db +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Komga +Not installed + +A comic and manga server with OPDS support + +~200M +Runs on Pi +Needs a hard drive +Install +Details +LubeLogger +Not installed + +The family car's services, repairs and fuel-ups in one place + +~80M +Runs on Pi +Install +Details +MeTube +Not installed + +Download videos and audio to your drive, for your family + +~128M +Runs on Pi +Needs a hard drive +Install +Details +Mealie +Not installed + +A recipe manager and meal planner + +~200M +Runs on Pi +Install +Details +Navidrome +Not installed + +A light music server with Subsonic API support + +~50M +Runs on Pi +Needs a hard drive +Install +Details +Nextcloud +Not installed + +Your own cloud storage - a Google Drive/Dropbox alternative + +~256M +Needs a hard drive +Install +Details +OnlyOffice +Not installed + +A full office suite in your browser + +~512M +Install +Details +OpenGist +gist.enkisfelhom.hu +↗ +Running +Up to date + +Share code snippets (a GitHub Gist alternative) + +~30M +Runs on Pi +opengist +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Outline +Not installed + +A modern team knowledge base with Markdown + +~200M +Install +Details +Paperless-ngx +papir.enkisfelhom.hu +↗ +Running +Up to date + +Digitise your documents and keep them in order + +~500M +Runs on Pi +Needs a hard drive +NVME 1TB +paperless-webserver +Up 10 hours (healthy) +paperless-redis +Up 10 hours (healthy) +paperless-postgres +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Papra +Not installed + +A minimalist document store and organiser + +~256M +Runs on Pi +Install +Details +Plex +Not installed + +A popular media server with a polished interface + +~512M +Needs a hard drive +Install +Details +PrivateBin +paste.enkisfelhom.hu +↗ +Running +Up to date + +Encrypted note and text sharing + +~30M +Runs on Pi +privatebin +Up 10 hours (healthy) +Update +Restart +Stop +Logs +Details +Radarr +Not installed + +Films download and file themselves + +~150M +Runs on Pi +Needs a hard drive +Install +Details +Radicale +Not installed + +Calendar and contacts on your own server (CalDAV / CardDAV) + +~40M +Runs on Pi +Install +Details +Rallly +Not installed + +Vote on a date (a Doodle alternative) + +~384M +Runs on Pi +Install +Details +Recipe Importer +Not installed + +Import Hungarian recipe sites into Mealie and Tandoor + +~30M +Runs on Pi +Install +Details +RomM +jatek.enkisfelhom.hu +↗ +Stopped +Up to date + +Retro game collection manager + +~300M +Needs a hard drive +NVME 1TB +We stopped the app because it kept crashing (or ran out of memory). Press Start to try again. +Start +Logs +Details +Sonarr +Not installed + +Series download and file themselves + +~150M +Runs on Pi +Needs a hard drive +Install +Details +SparkyFitness +Not installed + +A food and exercise tracker + +~400M +Install +Details +Tandoor Recipes +Not installed + +A recipe manager and meal planner + +~512M +Runs on Pi +Install +Details +Termix +Not installed + +SSH and server management in a browser + +~30M +Runs on Pi +Install +Details +Traefik +Running + +Traffic router (reverse proxy) — sends each request to the right app. + +traefik +Up 10 hours +Protected system component +Restart +Details +Uptime Kuma +Not installed + +Watch your services and websites + +~50M +Runs on Pi +Install +Details +Vaultwarden +Not installed + +A password manager (Bitwarden-compatible) + +~50M +Runs on Pi +Install +Details +Vikunja +Not installed + +Task lists and boards (a Todoist/Trello alternative) + +~50M +Runs on Pi +Install +Details +Wanderer +Not installed + +A hike planner that follows your tracks + +~350M +Runs on Pi +Install +Details +Wishlist +Not installed + +Wish lists shared across the household + +~120M +Runs on Pi +Install +Details +Zipline +Not installed + +A ShareX/Flameshot server - screenshots and file sharing + +~100M +Install +Details +n8n +Not installed + +Workflow automation with a visual editor + +~512M +Install +Details + Felhom.eu + Felhom.eu +Language + + /stacks + + /stacks +Restore an app from an exported package +Refresh the templates from the central catalog + + +travel.enkisfelhom.hu +This app is running the newest version available. + + +pdf.enkisfelhom.hu +This app is running the newest version available. + +wiki.enkisfelhom.hu +This app is running the newest version available. + + +books.enkisfelhom.hu +This app is running the newest version available. +Data storage: NVME 1TB + + + + + + +docs.enkisfelhom.hu +This app is running the newest version available. + + +files.enkisfelhom.hu + + + + + + + + + + + + + + + + +time.enkisfelhom.hu +This app is running the newest version available. + + + + + + + + +gist.enkisfelhom.hu +This app is running the newest version available. + + +papir.enkisfelhom.hu +This app is running the newest version available. +Data storage: NVME 1TB + + + +paste.enkisfelhom.hu +This app is running the newest version available. + + + + + +jatek.enkisfelhom.hu +This app is running the newest version available. +Data storage: NVME 1TB + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/apps-hu.txt b/documentation/audits/register-shrink-2026-10-10/r910/apps-hu.txt new file mode 100644 index 00000000..496d9427 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/apps-hu.txt @@ -0,0 +1,769 @@ +Indítópult +Vezérlőpult +Alkalmazások +Tárhely +Meghajtók +Hálózati tárhely +Biztonsági mentés +Áttekintés +Távoli mentés +Alkalmazások +Visszaállítás +Megosztás +Hálózati megosztás +Rendszermonitor +Hibakeresés +BEÁLLÍTÁSOK +Rendszer +Értesítések +Biztonság és hozzáférés +0.305.0 +Kijelentkezés ↗ +Alkalmazások +enkisfelhom.hu +Importálás +↻ Sablonok frissítése +Mind (61) +Futó (12) +Leállítva (1) +Telepíthető (48) +ActualBudget +Nincs telepítve + +Személyes pénzügyek és költségvetés kezelése + +~50M +Pi kompatibilis +Telepítés +Részletek +AdventureLog +travel.enkisfelhom.hu +↗ +Fut +Naprakész + +Utazási napló és kalandtervező + +~100M +Pi kompatibilis +adventurelog +Up 10 hours (healthy) +adventurelog-frontend +Up 10 hours (healthy) +adventurelog-postgres +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Audiobookshelf +Nincs telepítve + +Hangoskönyv és podcast kezelő szerver + +~100M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +BentoPDF +pdf.enkisfelhom.hu +↗ +Fut +Naprakész + +Adatvédelmi fókuszú PDF eszköztár + +~100M +Pi kompatibilis +bentopdf +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +BookStack +wiki.enkisfelhom.hu +↗ +Fut +Naprakész + +Egyszerű, könyv-szerű wiki és dokumentáció platform + +~150M +Pi kompatibilis +bookstack +Up 10 hours (healthy) +bookstack-db +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Cal.com +Nincs telepítve + +Nyílt forráskódú időpontfoglaló (Calendly alternatíva) + +~200M +Telepítés +Részletek +Calibre-Web Automated +books.enkisfelhom.hu +↗ +Fut +Naprakész + +Automatizált e-könyv könyvtár - konvertálás, metaadat kezelés és webes olvasó + +~200M +HDD szükséges +NVME 1TB +calibre-web +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Claper +Nincs telepítve + +Interaktív prezentáció és közönség bevonás + +~100M +Pi kompatibilis +Telepítés +Részletek +Cloudflare Tunnel +Fut + +Biztonságos internetkapcsolat — a szerver portnyitás nélkül érhető el kívülről. + +cloudflared +Up 10 hours (healthy) +Védett rendszerkomponens +Újraindítás +Részletek +Code-Server +Nincs telepítve + +VS Code a böngészőben - kódolás bárhonnan + +~200M +Telepítés +Részletek +Crafty Controller +Nincs telepítve + +Minecraft szerver kezelő webes felülettel + +~256M +Telepítés +Részletek +Dawarich +Nincs telepítve + +Saját helyelőzmények térképen - a Google Idővonal helyett + +~500M +Telepítés +Részletek +Docmost +docs.enkisfelhom.hu +↗ +Fut +Naprakész + +Modern wiki és dokumentáció platform (Notion-szerű) + +~200M +docmost +Up 10 hours (healthy) +docmost-postgres +Up 10 hours (healthy) +docmost-redis +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Emby +Nincs telepítve + +Személyes média szerver élő TV és DVR támogatással + +~512M +HDD szükséges +Telepítés +Részletek +FileBrowser +files.enkisfelhom.hu +↗ +Fut + +Fájlkezelő — a tárhely fájljainak böngészése a böngészőből. + +filebrowser +Up 10 hours (healthy) +Védett rendszerkomponens +Újraindítás +Részletek +Ghost +Nincs telepítve + +Professzionális blog és hírlevél platform + +~150M +Telepítés +Részletek +Gitea +Nincs telepítve + +Könnyű, saját Git szerver webes felülettel + +~100M +Pi kompatibilis +Telepítés +Részletek +Glance +Nincs telepítve + +Minimalista információs dashboard + +~20M +Pi kompatibilis +Telepítés +Részletek +Gokapi +Nincs telepítve + +Ideiglenes fájlmegosztás lejáró linkekkel + +~30M +Pi kompatibilis +Telepítés +Részletek +Grafana +Nincs telepítve + +Professzionális monitoring és vizualizációs platform + +~100M +Pi kompatibilis +Telepítés +Részletek +Gramps Web +Nincs telepítve + +Családfa készítő és genealógiai szoftver + +~384M +Pi kompatibilis +Telepítés +Részletek +Grimmory +Nincs telepítve + +E-könyvtár böngészőben olvasóval, OPDS-sel, Kobo és KOReader szinkronnal + +~600M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +Grocy +Nincs telepítve + +Háztartásvezetés: mi van otthon, mi fogy el, bevásárlólista és házimunkák + +~60M +Pi kompatibilis +Telepítés +Részletek +Home Assistant +Nincs telepítve + +Nyílt forráskódú okos otthon központ + +~256M +Pi kompatibilis +Telepítés +Részletek +Homebox +Nincs telepítve + +Otthoni leltár és eszköznyilvántartás + +~50M +Pi kompatibilis +Telepítés +Részletek +Homepage +Nincs telepítve + +Személyre szabható kezdőlap szolgáltatás widgetekkel + +~50M +Pi kompatibilis +Telepítés +Részletek +Immich +Nincs telepítve + +Fotók és videók kezelése (Google Photos alternatíva) + +~2048M +HDD szükséges +Telepítés +Részletek +Jellyfin +Nincs telepítve + +Ingyenes és nyílt forráskódú média szerver + +~512M +HDD szükséges +Telepítés +Részletek +Jellyseerr +Nincs telepítve + +Média igénylés kezelő Jellyfin/Plex integrációval + +~100M +Pi kompatibilis +Telepítés +Részletek +Karakeep +Nincs telepítve + +Könyvjelzők, cikkek, jegyzetek és képek egy helyen, kereshetően + +~700M +Telepítés +Részletek +Kimai +time.enkisfelhom.hu +↗ +Fut +Naprakész + +Időkövetés és projektmenedzsment + +~100M +Pi kompatibilis +kimai +Up 10 hours (healthy) +kimai-db +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Komga +Nincs telepítve + +Képregény és manga szerver OPDS támogatással + +~200M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +LubeLogger +Nincs telepítve + +A családi autó szerviz-, javítás- és tankolásnyilvántartása + +~80M +Pi kompatibilis +Telepítés +Részletek +MeTube +Nincs telepítve + +Videók és hanganyagok letöltése a meghajtódra, a család számára + +~128M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +Mealie +Nincs telepítve + +Receptkezelő és étkezéstervező + +~200M +Pi kompatibilis +Telepítés +Részletek +Navidrome +Nincs telepítve + +Könnyű zene szerver Subsonic API támogatással + +~50M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +Nextcloud +Nincs telepítve + +Saját felhő tárhely - Google Drive/Dropbox alternatíva + +~256M +HDD szükséges +Telepítés +Részletek +OnlyOffice +Nincs telepítve + +Teljes értékű irodai csomag a böngészőben + +~512M +Telepítés +Részletek +OpenGist +gist.enkisfelhom.hu +↗ +Fut +Naprakész + +Kód snippetek megosztása (GitHub Gist alternatíva) + +~30M +Pi kompatibilis +opengist +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Outline +Nincs telepítve + +Modern csapat tudásbázis Markdown támogatással + +~200M +Telepítés +Részletek +Paperless-ngx +papir.enkisfelhom.hu +↗ +Fut +Naprakész + +Dokumentumok digitalizálása és rendszerezése + +~500M +Pi kompatibilis +HDD szükséges +NVME 1TB +paperless-webserver +Up 10 hours (healthy) +paperless-redis +Up 10 hours (healthy) +paperless-postgres +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Papra +Nincs telepítve + +Minimalista dokumentumtár és rendszerező + +~256M +Pi kompatibilis +Telepítés +Részletek +Plex +Nincs telepítve + +Népszerű média szerver csiszolt felülettel + +~512M +HDD szükséges +Telepítés +Részletek +PrivateBin +paste.enkisfelhom.hu +↗ +Fut +Naprakész + +Titkosított jegyzet és szöveg megosztás + +~30M +Pi kompatibilis +privatebin +Up 10 hours (healthy) +Frissítés +Újraindítás +Leállítás +Naplók +Részletek +Radarr +Nincs telepítve + +Automatikus film letöltő és rendszerező + +~150M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +Radicale +Nincs telepítve + +Naptár és névjegyek a saját szervereden (CalDAV / CardDAV) + +~40M +Pi kompatibilis +Telepítés +Részletek +Rallly +Nincs telepítve + +Időpont szavazás (Doodle alternatíva) + +~384M +Pi kompatibilis +Telepítés +Részletek +Recipe Importer +Nincs telepítve + +Magyar receptoldalak importálása Mealie-be és Tandoor-ba + +~30M +Pi kompatibilis +Telepítés +Részletek +RomM +jatek.enkisfelhom.hu +↗ +Leállítva +Naprakész + +Retró játékgyűjtemény kezelő + +~300M +HDD szükséges +NVME 1TB +Az alkalmazást leállítottuk, mert újra és újra összeomlott (vagy elfogyott a memóriája). Az Indítás gombbal újra megpróbálhatod. +Indítás +Naplók +Részletek +Sonarr +Nincs telepítve + +Automatikus sorozat letöltő és rendszerező + +~150M +Pi kompatibilis +HDD szükséges +Telepítés +Részletek +SparkyFitness +Nincs telepítve + +Táplálkozás- és edzéskövető + +~400M +Telepítés +Részletek +Tandoor Recipes +Nincs telepítve + +Receptkezelő és étkezés tervező + +~512M +Pi kompatibilis +Telepítés +Részletek +Termix +Nincs telepítve + +Webes SSH és szerver menedzser + +~30M +Pi kompatibilis +Telepítés +Részletek +Traefik +Fut + +Forgalomirányító (reverse proxy) — a kéréseket a megfelelő alkalmazáshoz irányítja. + +traefik +Up 10 hours +Védett rendszerkomponens +Újraindítás +Részletek +Uptime Kuma +Nincs telepítve + +Szolgáltatás és weboldal monitoring + +~50M +Pi kompatibilis +Telepítés +Részletek +Vaultwarden +Nincs telepítve + +Jelszókezelő (Bitwarden-kompatibilis) + +~50M +Pi kompatibilis +Telepítés +Részletek +Vikunja +Nincs telepítve + +Feladatkezelő listák és táblák (Todoist/Trello alternatíva) + +~50M +Pi kompatibilis +Telepítés +Részletek +Wanderer +Nincs telepítve + +Túra tervező és nyomkövetéssel + +~350M +Pi kompatibilis +Telepítés +Részletek +Wishlist +Nincs telepítve + +Családi kívánságlista megosztás + +~120M +Pi kompatibilis +Telepítés +Részletek +Zipline +Nincs telepítve + +ShareX/Flameshot szerver - screenshot és fájlmegosztás + +~100M +Telepítés +Részletek +n8n +Nincs telepítve + +Workflow automatizálás vizuális szerkesztővel + +~512M +Telepítés +Részletek + Felhom.eu + Felhom.eu +Nyelv + + /stacks + + /stacks +Alkalmazás visszaállítása exportált csomagból +Sablonok frissítése a központi katalógusból + + +travel.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + +pdf.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + +wiki.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + +books.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. +Adattároló: NVME 1TB + + + + + + +docs.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + +files.enkisfelhom.hu + + + + + + + + + + + + + + + + +time.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + + + + + + + +gist.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + +papir.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. +Adattároló: NVME 1TB + + + +paste.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. + + + + + +jatek.enkisfelhom.hu +Ez az alkalmazás a legfrissebb elérhető változatot futtatja. +Adattároló: NVME 1TB + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/backups-en.txt b/documentation/audits/register-shrink-2026-10-10/r910/backups-en.txt new file mode 100644 index 00000000..90e5dff8 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/backups-en.txt @@ -0,0 +1,159 @@ +Launcher +Dashboard +Apps +Storage +Drives +Network storage +Backup +Overview +Remote backup +Apps +Restore +Sharing +Network sharing +System monitor +Debug +SETTINGS +System +Notifications +Security and access +0.307.0 +Sign out ↗ +Backup — Apps +enkisfelhom.hu +Some apps have no off-site copy. Turn it on for all of them? (1) Yes, all of them Not now +App backups (database + configuration) + +A detailed backup of each app — database dumps, settings and app files. Independent of the full backup above, it can be restored per app. + +Schedule +Database backup +02:30 +Next: tomorrow 02:30 +Last database backup: +2026-10-10 04:15 (11 hours ago) +Back up now +Databases +APP TYPE SIZE LAST VALIDATION STATUS +adventurelog PostgreSQL 12.5 MB 04:15 49 tables OK +bookstack MariaDB 57.8 KB 04:15 42 tables OK +docmost PostgreSQL 157.6 KB 04:15 50 tables OK +kimai MariaDB 51.3 KB 04:15 35 tables OK +paperless-ngx PostgreSQL 590.4 KB 04:15 72 tables OK +romm MariaDB 113.0 KB 04:15 36 tables OK +Backup status of the apps +AdventureLog +Config + DB + Data +▶ +BentoPDF +Config +▶ +BookStack +Config + DB + Data +▶ +Calibre-Web Automated +Config + Data +▶ +Docmost +Config + DB + Data +▶ +Kimai +Config + DB + Data +▶ +OpenGist +Config + Data +▶ +Paperless-ngx +NVME 1TB +28.1 KB +▶ +PrivateBin +Config + Data +▶ +RomM +NVME 1TB +8.0 KB +▶ + Felhom.eu + Felhom.eu +Language + + /backups/apps + + /backups/apps + + + +App data backup is fine +The backup is a browsable file system + + adventurelog + + adventurelog + +App data backup is fine +We have no result for this backup. +The backup is a browsable file system + + bentopdf + + bentopdf + +App data backup is fine +The backup is a browsable file system + + bookstack + + bookstack + +App data backup is fine +We have no result for this backup. +The backup is a browsable file system + + calibre-web + + calibre-web + +App data backup is fine +The backup is a browsable file system + + docmost + + docmost + +App data backup is fine +The backup is a browsable file system + + kimai + + kimai + +App data backup is fine +We have no result for this backup. +The backup is a browsable file system + + opengist + + opengist + +App data backup is fine +The backup is a browsable file system + + paperless-ngx + + paperless-ngx + +App data backup is fine +We have no result for this backup. +The backup is a browsable file system + + privatebin + + privatebin + +We stopped the app because it kept crashing (or ran out of memory). Press Start to try again. +The backup is a browsable file system + + romm + + romm \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/backups-hu-0.305.0-duplicates.png b/documentation/audits/register-shrink-2026-10-10/r910/backups-hu-0.305.0-duplicates.png new file mode 100644 index 00000000..1ade099d Binary files /dev/null and b/documentation/audits/register-shrink-2026-10-10/r910/backups-hu-0.305.0-duplicates.png differ diff --git a/documentation/audits/register-shrink-2026-10-10/r910/backups-hu.txt b/documentation/audits/register-shrink-2026-10-10/r910/backups-hu.txt new file mode 100644 index 00000000..b7c5504d --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/backups-hu.txt @@ -0,0 +1,159 @@ +Indítópult +Vezérlőpult +Alkalmazások +Tárhely +Meghajtók +Hálózati tárhely +Biztonsági mentés +Áttekintés +Távoli mentés +Alkalmazások +Visszaállítás +Megosztás +Hálózati megosztás +Rendszermonitor +Hibakeresés +BEÁLLÍTÁSOK +Rendszer +Értesítések +Biztonság és hozzáférés +0.307.0 +Kijelentkezés ↗ +Biztonsági mentés — Alkalmazások +enkisfelhom.hu +Van alkalmazás, amelyről nem készül távoli mentés. Bekapcsolod mindegyikre? (1) Igen, mindegyikre Nem most +Alkalmazás-mentések (adatbázis + konfiguráció) + +Az egyes alkalmazások részletes, granulált mentése — adatbázis-kiírások, beállítások és alkalmazás-fájlok. A fenti teljes mentéstől függetlenül, alkalmazásonként visszaállítható. + +Ütemezés +Adatbázis mentés +02:30 +Következő: holnap 02:30 +Utolsó adatbázis mentés: +2026-10-10 04:15 (11 órája) +Mentés most +Adatbázisok +ALKALMAZÁS TÍPUS MÉRET UTOLSÓ ÉRVÉNYESÍTÉS ÁLLAPOT +adventurelog PostgreSQL 12.5 MB 04:15 49 tábla OK +bookstack MariaDB 57.8 KB 04:15 42 tábla OK +docmost PostgreSQL 157.6 KB 04:15 50 tábla OK +kimai MariaDB 51.3 KB 04:15 35 tábla OK +paperless-ngx PostgreSQL 590.4 KB 04:15 72 tábla OK +romm MariaDB 113.0 KB 04:15 36 tábla OK +Alkalmazások mentési állapota +AdventureLog +Konfig + DB + Adatok +▶ +BentoPDF +Konfig +▶ +BookStack +Konfig + DB + Adatok +▶ +Calibre-Web Automated +Konfig + Adatok +▶ +Docmost +Konfig + DB + Adatok +▶ +Kimai +Konfig + DB + Adatok +▶ +OpenGist +Konfig + Adatok +▶ +Paperless-ngx +NVME 1TB +28.1 KB +▶ +PrivateBin +Konfig + Adatok +▶ +RomM +NVME 1TB +8.0 KB +▶ + Felhom.eu + Felhom.eu +Nyelv + + /backups/apps + + /backups/apps + + + +Alkalmazás-adat mentés rendben +A mentés böngészhető fájlrendszerben + + adventurelog + + adventurelog + +Alkalmazás-adat mentés rendben +Erről a mentésről nincs eredményünk. +A mentés böngészhető fájlrendszerben + + bentopdf + + bentopdf + +Alkalmazás-adat mentés rendben +A mentés böngészhető fájlrendszerben + + bookstack + + bookstack + +Alkalmazás-adat mentés rendben +Erről a mentésről nincs eredményünk. +A mentés böngészhető fájlrendszerben + + calibre-web + + calibre-web + +Alkalmazás-adat mentés rendben +A mentés böngészhető fájlrendszerben + + docmost + + docmost + +Alkalmazás-adat mentés rendben +A mentés böngészhető fájlrendszerben + + kimai + + kimai + +Alkalmazás-adat mentés rendben +Erről a mentésről nincs eredményünk. +A mentés böngészhető fájlrendszerben + + opengist + + opengist + +Alkalmazás-adat mentés rendben +A mentés böngészhető fájlrendszerben + + paperless-ngx + + paperless-ngx + +Alkalmazás-adat mentés rendben +Erről a mentésről nincs eredményünk. +A mentés böngészhető fájlrendszerben + + privatebin + + privatebin + +Az alkalmazást leállítottuk, mert újra és újra összeomlott (vagy elfogyott a memóriája). Az Indítás gombbal újra megpróbálhatod. +A mentés böngészhető fájlrendszerben + + romm + + romm \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/shots.js b/documentation/audits/register-shrink-2026-10-10/r910/shots.js new file mode 100644 index 00000000..5ffc1928 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/shots.js @@ -0,0 +1,39 @@ +// R-910 (2026-10-10): the website's dashboard pictures retaken on demo-hp's household guest 9201, controller 0.305.0+, read only +// except the language round trip (through the globe's own form). Starts in whatever language the household has, shoots both +// languages, ends in the language it found. Each page's visible text is saved beside its picture for the privacy scan. +const puppeteer = require('puppeteer'); const fs = require('fs'); +const sleep = ms => new Promise(r => setTimeout(r, ms)); +const HOST = 'felhom.enkisfelhom.hu', BASE = 'https://' + HOST; +const sess = fs.readFileSync('/in/session.txt', 'utf8').trim(); const [k, v] = sess.split('='); +const PAGES = [['start', '/launcher'], ['apps', '/stacks'], ['app', '/apps/paperless-ngx'], ['backups', '/backups/apps']]; +async function lang(p) { return p.$eval('html', e => e.lang); } +async function setLang(p, want) { + await p.goto(BASE + '/launcher', {waitUntil: 'networkidle2'}); + const btn = await p.$(`button.lang-globe-item[value="${want}"]`); + if (!btn) throw new Error('no globe item ' + want); + await Promise.all([p.waitForNavigation({waitUntil: 'networkidle2'}).catch(() => {}), p.evaluate(b => b.closest('form').requestSubmit(b), btn)]); + await p.goto(BASE + '/launcher', {waitUntil: 'networkidle2'}); + console.log('language now', await lang(p)); +} +async function shot(p, name) { + await sleep(1500); + await p.screenshot({path: `/out/${name}.png`}); + const t = await p.evaluate(() => document.body.innerText + '\n' + Array.from(document.querySelectorAll('[title],[alt],[placeholder],input[value]')).map(e => [e.title, e.alt, e.placeholder, e.value].join(' ')).join('\n')); + fs.writeFileSync(`/out/${name}.txt`, t); console.log('shot', name); +} +(async () => { + const b = await puppeteer.launch({args: ['--no-sandbox', '--ignore-certificate-errors', `--host-resolver-rules=MAP * ${process.env.TRAEFIK}`]}); + const p = await b.newPage(); + await p.setCookie({name: k, value: v, domain: HOST, path: '/', secure: true, httpOnly: true}); + await p.setViewport({width: 1440, height: 900, deviceScaleFactor: 1}); + await p.goto(BASE + '/launcher', {waitUntil: 'networkidle2'}); + const found = await lang(p); console.log('language found', found); + const order = found === 'hu' ? ['hu', 'en'] : ['en', 'hu']; + for (const l of order) { + if ((await lang(p)) !== l) await setLang(p, l); + for (const [name, path] of PAGES) { await p.goto(BASE + path, {waitUntil: 'networkidle2'}); await shot(p, `${name}-${l}`); } + } + if ((await lang(p)) !== found) await setLang(p, found); + console.log('language left', await lang(p), '(found', found + ')'); + await b.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/documentation/audits/register-shrink-2026-10-10/r910/start-en.txt b/documentation/audits/register-shrink-2026-10-10/r910/start-en.txt new file mode 100644 index 00000000..082fc0c3 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/start-en.txt @@ -0,0 +1,55 @@ +Launcher +Dashboard +Apps +Storage +Drives +Network storage +Backup +Overview +Remote backup +Apps +Restore +Sharing +Network sharing +System monitor +Debug +SETTINGS +System +Notifications +Security and access +0.305.0 +Sign out ↗ +Launcher +enkisfelhom.hu +Share the launcher +AdventureLog +BentoPDF +BookStack +Calibre-Web Automated +Docmost +Filebrowser +Kimai +OpenGist +Paperless-ngx +PrivateBin +RomM +Stopped + Felhom.eu + Felhom.eu +Language + + /launcher + + /launcher + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/audits/register-shrink-2026-10-10/r910/start-hu.txt b/documentation/audits/register-shrink-2026-10-10/r910/start-hu.txt new file mode 100644 index 00000000..966fd9c1 --- /dev/null +++ b/documentation/audits/register-shrink-2026-10-10/r910/start-hu.txt @@ -0,0 +1,55 @@ +Indítópult +Vezérlőpult +Alkalmazások +Tárhely +Meghajtók +Hálózati tárhely +Biztonsági mentés +Áttekintés +Távoli mentés +Alkalmazások +Visszaállítás +Megosztás +Hálózati megosztás +Rendszermonitor +Hibakeresés +BEÁLLÍTÁSOK +Rendszer +Értesítések +Biztonság és hozzáférés +0.305.0 +Kijelentkezés ↗ +Indítópult +enkisfelhom.hu +Indítópult megosztása +AdventureLog +BentoPDF +BookStack +Calibre-Web Automated +Docmost +Filebrowser +Kimai +OpenGist +Paperless-ngx +PrivateBin +RomM +Leállítva + Felhom.eu + Felhom.eu +Nyelv + + /launcher + + /launcher + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index b69be0e1..1fbc269b 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -26,6 +26,26 @@ --- +## 2026-10-10 (afternoon) — the register-shrink session: delivered rows read back, small fixes, the decision sheet + +The full text of the rows below: `git show dee4e0aa0c:documentation/backlog/OPEN-ITEMS.md`. Evidence: `documentation/audits/register-shrink-2026-10-10/` unless a row names another place. + +| Row | What | Closed | Evidence | +|---|---|---|---| +| **R-906** | **A page can show „+ 5 további figyelmeztetés" (+5 more warnings) with no warning above it.** (P4) | CLOSED 2026-10-10 — **released in controller 0.304.0 and read live.** On scratch 9202 (0.304.0, the same nine inline disk warnings that caused it) the raw alert list is five `disk-not-separate` + one `overflow` (`/api/debug/dump`), and NO page — /launcher, /stacks, /backups, /monitoring — renders a „+ N további figyelmeztetés” line; each shows its 3 real banners. Second channel: demo-hp 9201 and Tester 1 run 0.305.0 (`docker ps`). | controller `d5f2e47` (in 0.304.0); `TestR906_*` | +| **R-907** | **On a phone (390 px) the Launcher's „Indítópult megosztása" / „Share the launcher" button runs off the right edge of the screen.** (P4) | CLOSED 2026-10-10 — **released in 0.304.0 and measured on a real box.** demo-hp's household guest 9201, controller 0.305.0, headless Chrome 390 × 844 (read only): layout width 390 = screen, the „Share the launcher” button at x 16–192 with its text whole (was 424 / 395 off the edge). | `r907-r909-demo-hp-readback.txt`, `r907-launcher-390.png`; `TestR907_PageHeaderWrapsOnAPhone` | +| **R-909** | **On the Apps page an installed app's status tags covered its name, and the logo sat lower than on the other cards.** (P4) | CLOSED 2026-10-10 — **released in 0.304.0 and measured on a real box.** demo-hp 9201 (0.305.0), /stacks at 1440 and 390 px: 61 cards, a name under a tag on 0, one logo offset value (0 px), the address on one line on all. | `r907-r909-demo-hp-readback.txt`, `r909-stacks-1440.png`; `TestStackCardHeaderLayout` | +| **R-911** | **On an English page the „data storage not reachable" banner stays Hungarian: „Adattároló nem elérhető: ".** (P4) | CLOSED 2026-10-10 — **already fixed before the row was read** — controller `fcd9f09` (R-79 option A, D3, 2026-10-08 14:31, released in 0.304.0) gave the storage-unreachable warning its `MsgRef` (`health.storage_unavailable`, hu + en). Pinned by `TestR79_HealthBannersFollowTheHousehold` (English row „Storage not reachable: …”, run green 2026-10-10). The row was seen on a test build cut before that commit. | controller `fcd9f09`; `internal/web/r79_health_banner_test.go` | +| **R-815** | **First-ever GC on `felhom-offsite` (armed today 13:11 UTC, never run)** (P4) | CLOSED 2026-10-10 — **the GC has run every Sunday since July and every run ended OK.** Read on ep0 (read only): `proxmox-backup-manager garbage-collection status felhom-offsite` → last run Sun 2026-10-04 04:30:44, 44 s, 8.26 GiB removed, state OK, next 2026-10-11; the task log holds 12 `garbage_collection:felhom-offsite … OK` entries, the first 2026-07-27 17:17 UTC. | `r815-gc-tasks.txt` | +| **R-756** | **[P3-LOW] On 9202, "remove with drive data" refuses calibre-web with 409 „…/scratch_hdd/userdata/calibre-web tárhely jelenleg nem elérhető", while the controller container lists that folder.** (P3) | CLOSED 2026-10-10 — **not reachable through the product any more.** The refused `HDD_PATH` was a user folder inside the drive (`…/scratch_hdd/userdata/calibre-web`), the R-839 shape — a 2026-09-22 drill-harness value on 9202; since controller 0.298.0 the deploy API refuses such a value (R-839, `r839_hdd_subpath_test.go`). Read 2026-10-10 on 9202: calibre-web is not installed; the drive is a mount (`findmnt`). Loosening the check would weaken the boot start gate, so nothing else changes. | controller `4867ec1` (R-839) | +| **R-570** | **[P3-LOW] The off-site stale-note display still has a Hungarian-text fallback, for boxes that have not run off-site since v0.251.0.** (P4) | CLOSED 2026-10-10 — **condition met and the fallback deleted.** Each box's own `settings.json` read 2026-10-10: demo-hp, demo-felhom and Tester 1 `offbox.last_success` 2026-10-10 ~02:16Z on controller 0.304.0; Tester 2 was installed 2026-10-04 on a far newer controller. Controller `9e5b881` deletes the legacy text test and its constant; red-proof: the new no-kind row of `TestR553_StaleNoteDecisionSurvivesWordingChange` failed against the fallback. Delivered in controller 0.307.0 at 13:22Z to demo-hp, demo-felhom and Tester 1 (host journal „new controller healthy” + guest `0.307.0 starting`). Localisation may now translate the producer. | controller `9e5b881`, `fc6466a` | +| **R-896** | **The catalog's `volume-persistence` gate runs its canary and the apps on the LOCAL Docker, so a full gate run on DooPlex acts on the production host — and there its self-test fails for every app.** (P4) | CLOSED 2026-10-10 — **fixed in the catalog** (`1a642b4`, CI 1638 success): `check-volume-persistence.py` refuses (HARNESS REFUSED, exit 3) before any docker call unless the host says it is a scratch host — the bench's `/opt/upg`, or `FELHOM_SCRATCH_HOST=1`. DooPlex has neither. `TestVenue` red before the guard („the real prober was reached”); a new end-to-end decoy. The self-test failure was seen only on DooPlex, where the gate no longer runs; the bench runs it under its own canary. | catalog `1a642b4` | +| **R-912** | **No gate checks that every catalog app has a logo file under its slug.** (P4) | CLOSED 2026-10-10 — **gate built**: `site_gates.py` gate 21 — every catalog app (`app-catalog-felhom.eu/templates/*/.felhom.yml`, by its `slug:`) has `website/assets/-logo.svg` or `.png`; 60 of 60 today; an absent catalog clone is said out loud. Decoy `site/app-logo-under-another-name` (Crafty Controller's logo renamed `crafty-logo.png`, the measured shape) convicts with its own line. **The „no coloured logo” half is not built, on purpose:** a static read of the SVGs cannot decide colour (six white-rendering logos use black fills as masks, one uses an `feColorMatrix` filter); it needs a browser render, and the operator's eye on the website covers it. | `scripts/site_gates.py` gate 21; `scripts/test_gate_decoys.py` | +| **R-903** | **On a phone with JavaScript off, the website's menu does not open — so its links and the language globe cannot be reached there.** (P4) | CLOSED 2026-10-10 — **fixed, CSS only**: `@media (max-width: 900px) and (scripting: none)` in `site.css` (`?v=9` on all 18 pages) puts the menu links and the globe in the header as a wrapped row when JavaScript is off; script-on pages are unchanged. Measured in headless Chrome at 376 px, JavaScript OFF, `/` and `/en/`: reachable links **0/7 before, 7/7 after**; with JavaScript ON the closed off-canvas menu is as before. | `r903-phone-nojs.txt`, `r903-check.js` | +| **R-338** | **`demo-hp` is not on the R-50 island at all, and `operations/nodes.md` states that it is.** (P3) | CLOSED 2026-10-10 — **no longer true**: demo-hp IS on the R-50 island (read 2026-10-10 by a read-only helper): `agent.json` `local_api` = `listen_addr 169.254.253.1:8443`, `island_bridge vmbr9`, `island_guest_addr 169.254.253.2/30`; `ss -ltnp` shows the agent on `169.254.253.1:8443` only; `vmbr9` UP with member `veth9201i1`; guest 9201 has `eth1` on vmbr9. `operations/nodes.md` is true. The row's facts predate a reprovision. | `sheet-group3` read, this session | +| **R-916** | **The logo has no usable vector master: `website/assets/logo.svg` sets „felhom.eu" as live text in the fonts „M+ 2c" and „Vremena Grotesk", which DooPlex does not have, so every renderer here draws other letters.** (P4) | CLOSED 2026-10-10 — **no longer true**: `website/assets/logo.svg` has no live text — 0 of 3 text elements carry content, the wordmark is five `` elements — and it renders its lettering with NO font present: ImageMagick (not a browser) at 1600 px drew „felhom.eu” as the outlined shapes. No Inkscape pass is needed. (ImageMagick's own renderer drew a dark band where the cloud's gradient is; a renderer limit, not the file's.) | this session's render (1600 × 1012) | +| **R-910** | **The website's dashboard pictures show the old Apps card (the tags crowd the name, R-909) and no phone view (R-907).** (P4) | CLOSED 2026-10-10 — **all eight pictures retaken** on demo-hp's household guest 9201 (read only except the language round trip through the globe's own form; the household was found in English and left in English): Launcher, Apps, Paperless-ngx's page on 0.305.0, Backup → Apps on 0.307.0, hu + en, 1440 × 900, WebP q82. The Apps card now shows the R-909 layout. Retaking found a defect: on 0.305.0 the Backup → Apps table listed each database several times after a restart (the undo copies) — fixed in controller 0.307.0 and read back (6 rows for 6 databases); that picture was taken after the fix. Privacy scan of every page's text: 0 e-mails, 0 IPv4, only the test domain; long hex values are the hidden CSRF field. Phone view: not added (`captions-claims.md` keeps it off the site). | `r910/` (page texts, the script, the 0.305.0 duplicate picture), `databases-table-readback-0.307.0.txt` | + ## 2026-10-09 — the Meta app is Live: the Facebook Page can post in public The full text of the row below: `git show 625d38645c:documentation/backlog/OPEN-ITEMS.md`. diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 89fd8fa5..1647378f 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -122,7 +122,7 @@ stopping line that lies. | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | -## Apps & catalog — 11 rows (P3 3, P4 8) +## Apps & catalog — 9 rows (P3 2, P4 7) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -135,8 +135,6 @@ stopping line that lies. | **R-770** | Apps & catalog | P4 | **[P3-LOW] Invidious — fit check only; the recommendation is not to build it.** READ 2026-10-01: playback needs `invidious-companion` (rolling `latest`, no version tags); PostgreSQL 14 (EOL 2026-11); `registration_enabled: true` by default; upstream: a bot check means „your IP is blocked from YouTube”, a 429 can last 24 h, triggered by „someone on your network” — on our boxes that IP is the household's. One bad period in 2026 (March, ~2 weeks). No report found of a family's other devices being bot-checked (inference). **Needs:** the operator's go / no-go. `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator | | **R-771** | Apps & catalog | P4 | **[P3-LOW] moonlight-web — fit check only; not buildable through an HTTP-only tunnel at usable latency.** READ 2026-10-01: two unrelated projects (MrCreativ3001/moonlight-web-stream, the original; linckosz/moonlight-web); both need Sunshine/Apollo/Wolf on a gaming PC on the LAN and WebRTC over UDP (40000-40100/udp; linckosz recommends host networking and sends telemetry by default); both have a WebSocket fallback (high latency, all video through the tunnel); a logged-in user controls the PC's desktop. **Needs:** the operator's go / no-go (LAN-only use would need a different publishing model). `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator | | **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC | -| **R-907** | Apps & catalog | P4 | **On a phone (390 px) the Launcher's „Indítópult megosztása" / „Share the launcher" button runs off the right edge of the screen.** SEEN 2026-10-08 (website dashboard pictures, controller 0.303.0, demo-hp guest 9201, headless Chrome 390 × 844 @2): the page title, the domain chip and the button sit on one row; the button's text is cut at the right edge (`audits/website-dashboard-2026-10-08/README.md`). Not fixed here (controller out of scope for the website task). | **VERIFY — fixed on main 2026-10-08, ships with tomorrow's controller release** (controller `d5f2e47`, `423b6d3`). In the 768 px block `.page-header` wraps (the share button drops to its own line, text whole) and a banner wraps inside itself (its link ran past the screen once R-906 let the real banners through). `TestR907_PageHeaderWrapsOnAPhone` (red against 05e12921). Live on 9202, 390 px, hu + en: layout width 390 = screen, button right edge 217 / 192 px (was 424 / 395) | — | Release tomorrow; close with R-910's phone picture | CC | -| **R-909** | Apps & catalog | P4 | **On the Apps page an installed app's status tags covered its name, and the logo sat lower than on the other cards.** SEEN 2026-10-08 (website dashboard picture, demo-hp 9201; measured on 9202, 0.303.0, English 1440 px: Paperless-ngx's name text under a tag; logo top minus name top -5 / 9 / 21 px across cards; the address's „↗" on a second line). Cause: the title row and every tag shared one `space-between` flex row; the logo was centred on the name block. | **VERIFY — fixed on main 2026-10-08, ships with tomorrow's controller release** (controller `d5f2e47` + fixtures `e37e9b5`). Two rows on every card: logo + name (+ address, one line, ellipsis, full in `title`) over a wrapping tags row. `TestStackCardHeaderLayout` (red against 05e12921). Live on 9202 with a test build, 1440 + 390 px, hu + en: 61 cards, name under a tag on 0, logo offset one value (-3 px), address on one line | — | Release tomorrow; close with R-910's Apps picture | CC | ## App updates — 4 rows (P3 4) @@ -147,7 +145,7 @@ stopping line that lies. | **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC | | **R-785** | App updates | P3 | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | — | — | CC | -## Backup & restore — 31 rows (P2 4, P3 13, P4 14) +## Backup & restore — 29 rows (P2 4, P3 13, P4 12) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -175,25 +173,22 @@ stopping line that lies. | **R-336** | Backup & restore | P4 | **The offsite DR endpoint is polled about once per second, and that is what turned a slow leak into an outage.** ep0's PBS proxy served **~85,000 requests/day** — a flat **3,538/hour**, every hour, from two boxes: `74,445 GET /api2/json/admin/datastore` (`libwww-perl`, i.e. PVE's `pvestatd`) and `73,171 GET /admin/datastore/felhom-offsite/status` (`proxmox-backup-client`). Two pollers asking substantially the same question at the same rate. On 2026-08-18 this walked a connection leak in the proxy to its 1024-fd soft limit in **14 days**, wedging the offsite tier for 9½ hours (`audits/INCIDENT-ep0-pbs-fd-exhaustion-2026-08-18.md`). The `LimitNOFILE=65536` drop-in applied that morning raises the ceiling **but does not fix the leak** — it converts a fortnightly outage into a multi-year one, which is mitigation, not a fix. A DR endpoint that is written to weekly does not need to be asked about every second | **READY (M) — NEW 2026-08-18** **2026-10-06 night: no hub half** — the pollers are pvestatd and proxmox-backup-client on the boxes; the lever (disabling the storage entry) collides with the agent's pbsdr health model; still a design question. | — | **CORRECTED 2026-08-18 (evening) — the easy lever named here does not exist.** This cell used to read *"PVE storage status is the prime suspect, and its interval is tunable"*. **The first half is right and the second half is false.** `pvestatd` stats EVERY configured storage on each 10-second cycle, and Proxmox staff have stated the interval is not designed to be configurable — so there is no knob to turn down. The only lever PVE actually offers is disabling the storage entry (`pvesm set --disable 1`) around the backup window, and that is **substantially more than a tuning knob**: it collides with `felhom-agent/internal/pbsdr/manager.go`'s health model, where an inactive-but-existing entry drives the consume-the-one-time-secret recovery path. So the fix is a design question (does the hub still need a 15-minute fill reading at all, given R-339 now reports reachability separately?), not a config edit. **Doc-only correction — no agent code was changed.** The remaining step is unchanged: cut the poll rate by whatever means survives that question, then confirm the fd count between restarts stops climbing — the positive observable, per standing rule 3. **Baseline measured 2026-08-18, and the FIRST measurement published was WRONG.** The initial "~85/day, matching the ~73/day implied by the failure" came from a single 17-minute window whose delta was **one descriptor** — a sample of one cannot carry a daily rate, and the agreement that made it feel solid was coincidence. **Re-measured over two independent windows the same morning: 183/day (31 min) and 200/day (5.6 h)** — ~2.6x the published figure, putting the runway to the 65536 ceiling at **~357 days, not the ~2 years first claimed**. **And the named mechanism is the minority one:** across that window `CLOSE-WAIT` held flat at 1 while `ESTAB` grew 45→49 — *all* the growth was established connections, and at the wedge the split was 1011 ESTAB / 543 CLOSE-WAIT. **The fix must target connections the proxy never reaps, not just `CLOSE-WAIT` sockets.** The PBS 4.2.5-1 upgrade (2026-08-18) did NOT change the slope and was never expected to — see R-341 **SPIKE 2026-08-20 — THE PREMISE OF THIS ROW DOES NOT SURVIVE MEASUREMENT, and that is a change in what the row IS, not new evidence on it.** `audits/SPIKE-ep0-established-connections-2026-08-20.md`. **The leak is OURS, and the poll rate is not what feeds it.** Every one of the 388 leaked descriptors is an ESTABLISHED connection held open by **`felhom-agent`** on the boxes — 194 on each, `ss -tnp` naming a single PID per box, and **zero** held by `pvestatd` or `proxmox-backup-client`. Confirmed independently from ep0's access log over the same 46.18 h window: `libwww-perl` (pvestatd) **81,192 requests -> 0 descriptors**, `proxmox-backup-client` **80,061 requests -> 0 descriptors**, `Go-http-client/1.1` (the agent) **387 `/snapshots` calls -> 388 sockets — one per call, within one**. So **162,404 requests, 99.5% of the traffic, produce 0% of the leak.** **Mechanism, named from source:** `felhom-agent/internal/pbs/client.go:56-60` builds `&http.Transport{TLSClientConfig: tlsCfg}` — a composite literal, so `IdleConnTimeout` is the zero value = **no limit** (`http.DefaultTransport` sets 90 s; a literal does not inherit it) — and `cmd/felhom-agent/main.go:1486` (`pbsTargetsFromPVE`) builds **a fresh client every cycle**, as its own doc comment states. Each cycle therefore strands one idle keep-alive connection in a transport nothing ever closes; `CloseIdleConnections`/`IdleConnTimeout`/`MaxIdleConns` appear **nowhere** in the agent repo. Cadences reconcile without fitting: 900 s hub poll (184.7 cycles) + 6 h `DefaultVerifyCadence` (7.7 cycles) = 192.4 predicted vs **194 observed per box**. **CONSEQUENCE — RE-RANK.** The remaining step recorded above ("cut the poll rate, then confirm the fd count stops climbing") **would have produced a null result and read as a failed fix.** Cutting the Proxmox poll rate removes ~99.5% of ep0's request load and **zero** descriptors. The poll rate is still wrong on its own terms — 85,000 requests/day to a weekly-write DR endpoint — but it is now a **scaling/cost item, not the leak fix**, and the leak fix is **R-344**. **Q3 (is the leak proportional to the request rate?) is PREDICTED not-proportional and NOT YET MEASURED** — Phase C is held at STOP 1 with its prediction pre-registered in `evidence-ep0-established-connections-2026-08-20/phaseC-prediction.txt`. Do not record a proportionality verdict here until that window has run. **RE-SCOPED 2026-08-20 — THIS ROW IS NO LONGER A LEAK FIX, AND ITS RECORDED NEXT-STEP WOULD HAVE "FIXED" NOTHING WHILE LOOKING LIKE A FAILED FIX.** That near-miss is the reason the spike-first rule exists and it is kept here deliberately. The old next-step read: *cut the poll rate by whatever means survives that question, then confirm the fd count between restarts stops climbing.* Had it been executed, the fd count would have kept climbing at the same ~200/day, the poll reduction would have been recorded as ineffective, and the real defect — **ours, in `felhom-agent`, R-344** — would have been further from being found, not closer. **Measured 2026-08-20:** `pvestatd` (`libwww-perl`) and `proxmox-backup-client` made **162,404 requests** in a 46 h window and leaked **zero** descriptors; the agent made 811 and leaked **388**. The fix (agent 0.130.0) took ep0 from 388 accumulated descriptors to its **baseline of 17**, with the poll rate completely unchanged — 85,000/day before and after. **WHAT THIS ROW ACTUALLY IS NOW — a SCALING concern, still worth fixing on its own merits:** ~85,000 requests/day to a DR endpoint that is WRITTEN TO WEEKLY, from two boxes. That is ~42,500/box/day, so **at fifty customers it is ~2.1 million requests/day — about 25 requests/second, constantly, against a CX33**. The design question is unchanged and is still the hard part: does the hub still need a 15-minute fill reading at all, given R-339 reports reachability separately? And the lever remains awkward — `pvestatd` stats every configured storage on each 10-second cycle with no tunable interval, so the only PVE-side lever is disabling the storage entry, which collides with `felhom-agent/internal/pbsdr/manager.go`'s health model. **NEW ACCEPTANCE CRITERION, since the old one is void:** the fd count is NOT the observable for this row any more — that belongs to R-344 and is already satisfied. Measure the REQUEST RATE at ep0's access log, and state the projected rate at the target customer count. | CC | | **R-526** | Backup & restore | P4 | **[P3-LOW] A host delete cannot release only the customer's ep0 PBS token: the endpoint's one removal op destroys every backup group too.** MEASURED 2026-09-15 from source: `tenantsync.Deprovision` „DESTROYS the customer's PBS namespace, all its backup groups, and its token". The task asked for „PBS token elengedése" on host delete; building it needs a new token-only op in the ep0 tenantsync script — a new operation on a protected box. Not built. R-511's adopt path makes the kept token usable instead. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (new ep0 op yes/no), CC (build)** **Re-ranked 2026-10-03: P3->P4: operator teardown op on a protected box; adopt path covers the need.** | — | — | operator | | **R-541** | Backup & restore | P4 | **[P3-LOW] There is no path to move a customer between off-site boxes, or from shared to dedicated.** Read from source 2026-09-16: provisioning is idempotent-reuse keyed on the customer (`shared already provisioned for tester-1 (subaccount 311327)`), and a dedicated deprovision destroys the repository — so "move this customer" has no safe route today. It becomes reachable the moment R-540's second pool box exists, or when a customer outgrows the shared model. **Needs:** a move that copies the repository, re-keys, and only then releases the old sub-account — a new mechanism nobody has measured. | **READY — rank P3-LOW; owner: CC (hub) — design first** **Re-ranked 2026-10-03: P3->P4: needs a second pool box or an outgrown customer first; operator-only and far off (0.3% full).** | — | — | CC | -| **R-570** | Backup & restore | P4 | **[P3-LOW] The off-site stale-note display still has a Hungarian-text fallback, for boxes that have not run off-site since v0.251.0.** OPENED 2026-09-17 by R-553's fix: `offboxWarningDisplay` (controller/internal/web/handlers.go) decides on `LastWarningKind`, but a box upgraded to 0.251.0 carries the PERSISTED old sentence with no kind until its next off-site run rewrites it, so the substring test survives under `kind == ""`. **Close when every fleet box has completed one off-site run on ≥ 0.251.0** (the hub's reports carry the controller version; the off-site anchor is `offbox.last_success`), then delete the fallback, its constant and its legacy test rows. **Hard dependency: localisation slice 2 (R-557) must NOT translate the producer `"Sikeres — nincs mentésre jelölt alkalmazás"` (controller/internal/backup/offbox.go) until this row closes** — translating it while the fallback is load-bearing strands exactly those boxes. | **WATCHING - rank P3-LOW; owner: operator (the fleet condition), CC (the deletion)** **Re-ranked 2026-10-03: P3->P4: cleanup waiting on a fleet condition; no defect today.** | — | — | operator | | **R-691** | Backup & restore | P4 | **[P3-LOW] Kept data (09 §3 decision 36): two gaps of the first build.** (1) **The read-only file-browser view cannot open a folder another user owns with mode 0770** — nextcloud's `appdata/nextcloud` is `www-data` `drwxrwx---` (measured on 9202 2026-09-25), FileBrowser runs as uid 1000, so „Megőrzött adatok" shows the folder and not its files; the files are still listed, sized, loadable and deletable. Fix direction needs a decision (a read-only ACL, or a helper that lists as root) — not a chmod of the household's data. (2) **„Use my kept data" / Load looks only at the own unit (Tier 1) and the second-drive mirror (Tier 2)**; an app whose only database copy is off-site gets "no backup". Controller `43e99d1`. `audits/night-2026-09-26/E/` **-- 2026-09-25 live proof:** (1) confirmed on 9202 — the view mounts nextcloud's kept folders `:ro` but its files are `www-data` 0770. Also seen: the source's name „Megőrzött adatok" is Hungarian on an English box (the file browser's config holds one name). **-- 2026-09-27 (controller v0.275.0): (1) FIXED: the view joins the kept folder's OWNING GROUP when it is group-readable (never root's, never its own), binds stay `:ro`, nothing on disk changes (CC-unattended decision, `07` §6.5); the source's name follows a language switch (the switch re-syncs the file browser). Red-proofed, `audits/version-travel-2026-09-26/D3/`. NOT live-proven with a real nextcloud kept folder. STILL OPEN: (2), the Use/Load choice does not look at the off-site copy.** **-- 2026-09-27 (second session): (2) NOT built on purpose** — it composes the unit-only off-site download (`RestoreOffboxScratch(full=false)`) with the unit restore into a new restore path on household data, and no box CC may touch has an off-site target to prove it on (9202 has none; 9201 on both demo hosts is fenced). Needs: a Tier-0 guest with an off-site target, or an operator word to use one.** **-- 2026-09-28 (controller v0.277.0): (2) BUILT** — `KeptBestCopy` offers the off-site copy when it is newer than every local copy or the only one; the page names the copy and its date; `LoadKeptOffsite` downloads the unit alone, refuses a unit of another drive, with no data, or with no recorded data version (`07` §6.5/§6.6), then restores. Red-proofed (`audits/kept-offsite-2026-09-28/redproofs/`). Tier 1 regression live on 9202 (the choice named „saját mentés, 2026-09-28 10:06”, seed + file back). Floor 0.277.0, both demo boxes. **STILL OPEN: the live off-site proof** — a throwaway nextcloud on demo-hp 9201 joined the off-site copy 2026-09-28 10:15; its first snapshot runs the night of 09-28/29 (Part E (b)). Tier 2 not runnable live (9202 has one drive). **-- 2026-09-28 afternoon: LIVE-PROVEN on demo-hp 9201 (controller 0.278.0), endpoint level.** A throwaway nextcloud, seeded through its own front door, joined the off-site copy; the off-site run-now pushed snapshot `6cb379a8`. (a) The full off-site restore (prepare → download → reconstitute): 3 volumes + the database replayed, the seed read back, a marker user written after the snapshot read ABSENT, same versions. (b) Remove keeping the data + deleting the local copies → reinstall: the choice and the kept list both named "távoli mentés, 2026-09-28 15:40" / "the off-site copy, 2026-09-28 15:40"; "use my kept data" downloaded the unit alone and loaded 3/3 volumes + 1/1 database in 55 s; the seed and the kept files read back. App removed with its data; demo-hp's app list equals the list before. `audits/kept-offsite-2026-09-28/E/`. | **NARROWED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — the second-drive (Tier 2) path of Use/Load is not proven live — 9202 has one drive) — **CLOSED — controller v0.277.0, live 2026-09-28** | — | — | CC | -| **R-815** | Backup & restore | P4 | First-ever **GC** on `felhom-offsite` (armed today 13:11 UTC, never run) | **VERIFY** (2026-10-03 triage: a July watch row with no id; given R-815. WATCHING — no completion record found; schedule `sun 04:30` still present 2026-09-30.) — WATCHING | schedule | **Sun 2026-08-02 04:30 UTC** — confirm it completes | CC | | **R-816** | Backup & restore | P4 | **No off-site failure class has ever been seen live.** F-DIAG (controller v0.182.0, 2026-07-28) split off-site failures into six causes — quota, orphaned, no_repo, no_units, transport, unknown — each with its own Hungarian message. None of the six has been exercised by a real failure on a box; the recovery inventory records it only as a known limit (`documentation/architecture/_recovery-inventory-2026-07-28.md:955`). Filed 2026-10-03 from the F-DIAG row's residue when that row moved to `CLOSED-ITEMS.md`. | **READY — filed 2026-10-03 (triage); owner: CC.** Exercise each class once on a scratch guest (a full quota, a missing repository, a blocked transport) and read the message the household sees. | — | — | CC | | **R-832** | Backup & restore | P4 | **ep0's copy in a place outside both Hetzner and the operator's home (roadmap).** Today DooPlex (the operator's home) holds it (decision 71). A Hetzner Storage Box would share a provider with ep0 and with every household's file backups, and cannot run PBS, so the copy could not be verified or restored from directly. | **DEFERRED — later, if the product grows** | — | — | operator | | **R-878** | Backup & restore | P4 | **A catch-up (R-871) runs the database-dump leg in the DAY, and that leg stops an app with a volume for its copy — the household may notice the stop, and a large volume makes it longer.** MEASURED 2026-10-05 on demo-felhom: the catch-up at 08:25:02 stopped opengist, copied 182.5 KB, started it again — about 1 s, then a few seconds of `health: starting`; the night does exactly the same, unseen. Nothing measured for a large volume. Fix direction (if it matters): skip the volume copy of a running app in a DAYTIME catch-up and leave it to the next night, or warn. `audits/catchup-2026-10-05/partA/live-demo-felhom.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A LIVE MEASUREMENT** — a timed catch-up on 9202 for an app with a multi-GB volume, then the skip-or-warn choice. | — | measure a large volume first | CC | | **R-32** | Backup & restore | P4 | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** The rehearsal's S7 said in advance that an orphan card would BE a finding — and one appeared (16:58:14). Cause: RESET's `"hetzner":"ok"` leg destroys the sub-account, but **a Hetzner sub-account is an access-control object, not a data object** — its directory survives, so re-enabling offsite recreated an account over the previous lifecycle's ciphertext, encrypted under a key that same RESET had destroyed. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: design written** (`audits/night-burndown-2026-10-06/design-R-32.md`) — pick A: purge the repo + set-aside copies through the sub-account's own password login BEFORE deleting it (no main-account credential); the „repo data destroyed" comment at `hub/internal/offsite/offsite.go:276-280` is false for the shared tier; part (3) waits on a read-only measurement (`du` on port 23). Operator: agree to route A. **2026-10-07 07:58: `09` §3 decision 167 — option A yes, and delete the old RESET leftovers once.** **2026-10-07 (Part D step 2, read only):** the hub shows bx11 holding 3.3 GB of data while the four live customers account for ~0.6 GB → **~2.7 GB belongs to no live customer**; no live customer shows a set-aside folder, so the leftovers are in homes whose sub-account is gone, reachable only by the pool box's MAIN account. **The one-time clean-up is STOPPED:** no main-account login for bx11 is held on DooPlex (the API token in the credentials file sees no box). Needs: the main-account login, or the operator lists and deletes in the Hetzner console (`audits/day-2026-10-07/D/D2-listing-readonly.txt`). **2026-10-07: option A DELIVERED (hub 0.142.0):** RESET purges the repo and every `.orphaned-*` through the sub-account's own login before deleting it, and keeps the sub-account if the purge fails (red-proved, `audits/day-2026-10-07/D/`). Not yet seen live (no scratch RESET today). **The one-time clean-up of the ~2.7 GB of leftovers is STOPPED:** it needs the pool box's main-account login (see above). **2026-10-07 14:43 — `09` §3 decision 173: leave the ~2.7 GB of old leftovers for now; re-ranked P2 → P4.** Option A itself is delivered (hub 0.142.0). What stays open is ONLY the leftovers: they were computed (box total minus live customers), never listed, and deleting them needs the pool box's MAIN login, which reaches every customer's data. **Blocker: a safe main-account path.** | — | **Ruling from the run (three parts, deliberately separate):** (1) because RESET destroys custody, the ciphertext it leaves behind is unrecoverable **BY DESIGN** → RESET gains a **main-account purge of the customer base dir** (the existing operator ack already covers it); (2) the **move-aside guard STAYS** for reinstall-*without*-RESET — there custody survives and the card's "history recoverable" promise is true (R-26 depends on exactly that); (3) the operator **Restic tab shows per-customer directory bytes vs attributed snapshot bytes**, so dead data cannot hide. Measured on the pool box that night: **49 M attributed** (2 snapshots, 48.717 MiB) against **1.4 G + 3.0 M unattributed** across TWO `.orphaned-*` dirs. Evidence `restic-and-pool.txt` | CC | -## Storage & devices — 6 rows (P3 4, P4 2) +## Storage & devices — 4 rows (P3 2, P4 2) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-330** | Storage & devices | P3 | **Disk health Phase 2 — the three SMART attributes the wire does not carry.** The failing drive's most telling counter was **187 `Reported_Uncorrect`**, sitting at normalized **1** against threshold **0** with a raw count of **1001** — one point from failing and structurally unable to get there. Also wanted: **199 `UDMA_CRC_Error_Count`** (cabling) and **188 `Command_Timeout`**. None are on the agent→controller wire today, so v0.215.0's ladder could not use them. Phase 2 also persists periodic SMART **samples** (the right home is `metrics.MetricsStore`, NOT the Phase-1 state file, which is one record per disk and must stay that way). **This is a declared WIRE change, so under the G-1 gate the hub must model the new fields in the SAME session** — that is precisely why it was kept out of Phase 1, where it would have turned a one-word severity fix into a three-repo change | **READY (M) — NEW 2026-08-14** **2026-10-06 night: the wire half BUILT on main (unreleased):** the agent sends SMART 187 `reported_uncorrect`, 188 `command_timeout` (raw, vendor-packed on some drives — carried as reported), 199 `udma_crc_errors` (pointer + omitempty: unknown is absent, never 0); the controller decodes them and the hub's host-report mirror models them (G-1 wire gate green; it convicted the agent half alone). **Carried only** — no verdict, banner, mail or alarm reads them (`TestR330_CountersChangeNoVerdictYet`); using them changes what a household is told and is the next slice, with persisting samples. Red-proofs `audits/night-burndown-2026-10-06/r330/`. Ships with agent v0.150.0 and the next controller and hub releases. | R-328 (closed) | Add 187/199/188 to the agent's `SmartSummary` + hub model in one session; then persist samples | CC | | **R-332** | Storage & devices | P3 | **The new Hiba-from-counters path has never fired on real hardware.** v0.215.0's whole point is a verdict the product could not previously reach, and it is proven only against the committed fixture's values in unit tests (12 scenario groups, 11 of 12 red-proofs failing as required). The live validation on demo-hp proved the **negative** — three healthy disks still read Rendben across the deploy, no false alert — and the **severity wire** end to end, but no live disk has actually reached Hiba. **This is the honest gap and it must not be closed by pointing at the fixture tests**: the drive that produced the fixture is in DooPlex, which is Tier 2 and never a drill target, and the demo boxes are all-flash and healthy | **WATCHING — NEW 2026-08-14, NARROWED same day.** One item originally in this gap is now PROVEN LIVE: the **persisted state surviving a controller restart**. The v0.215.0→v0.216.0 redeploy destroyed and rebuilt the container, and the new one read back a `changed_at` written by the PREVIOUS version (`2026-08-14T07:23:14.640216851Z`, still intact at 09:31:35Z) instead of re-baselining — Scenario L on real hardware, not just the production-path unit test. **What remains unproven is the verdict itself, plus the stronger restart half: an already-ALERTED disk not re-alerting** | a real degrading disk, or an injection harness | **Closing condition:** a live disk reaching Hiba from counters, OR a deliberate injection through the REAL pipeline (agent `/disks` → controller check → hub event), not a hand-set verdict | CC | -| **R-756** | Storage & devices | P3 | **[P3-LOW] On 9202, "remove with drive data" refuses calibre-web with 409 „…/scratch_hdd/userdata/calibre-web tárhely jelenleg nem elérhető", while the controller container lists that folder.** MEASURED twice on 2026-10-01 (`audits/lockouts-2026-10-01/B/B1…`, `audits/calibre-name-and-prune-2026-10-01/A/A1…`): `POST /api/stacks/calibre-web/remove` with `remove_hdd_data` → 409; `docker exec felhom-controller ls -ld /mnt/felhom-drives/scratch_hdd/userdata/calibre-web` → the directory (dated 2026-09-22). The walk then removed the app keeping the data (R-442's fail-closed answer). Either the drive is not a registered drive on this scratch box (a test-venue artefact) or the resolver reads another path than the one it names. Not measured which. **-- 2026-10-01 (night, new apps):** the same 409 for Grimmory (`remove_hdd_data`), and a drive app's per-app backup is NOT offered for restore after a remove — the controller logged `drive … not mounted — skipping ensure (held by drive gate)`; the unit lives on that drive. So on 9202 checklist row 2.5 cannot be shown for any drive app until its scratch drive is a registered drive (`audits/new-apps-2026-10-01/box/grimmory/restore-why.txt`). **Checked from source 2026-10-05 (burn-down round 2):** Mechanism found in source: the refusal comes from felhom-controller@114ff27 controller/internal/stacks/delete.go:147-149 if !m.DriveLive(hddPath) -> msgDriveAbsentFmt with hddPath, and DriveLive is deploy.go:1007-1011 return m.isMountPoint(hddPath) -- it requires HDD_PATH ITSELF to be a mount point. Everywhere else HDD_PATH is compared to a registered storage path (api/router.go:574-575, web/handlers.go:3049, storage_handlers.go:528), i.e. a drive root. The message names .../scratch_hdd/use | **OPEN — rank P3-LOW; owner: CC** **2026-10-05 (burn-down night): NEEDS A LIVE READING.** The refusal shows that app's recorded HDD_PATH is a sub-folder of the drive (the R-839 shape); loosening the check would weaken the boot start gate. Next: that app's HDD_PATH and `findmnt` inside 9202. | — | — | CC | | **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC | | **R-352** | Storage & devices | P4 | **Four screens state something untrue about where an app's data goes, and the configured default is consulted by nothing that places data.** Measured on `demo-hp` 2026-08-21. **(1)** **40 of 53** catalogue templates declare no data path (`grep -rl 'env_var: HDD_PATH' --include='.felhom.yml'` → 13; total 53); those apps get **no storage field and no default** — their data lands in a named Docker volume on the system drive. **(2)** `GetDefaultStoragePath()` has exactly **three** non-test callers — the metrics collector (`cmd/controller/main.go:410`), the dashboard SystemInfo panel (`web/server.go:733`) and `.fab` import landing (`handler_export_upload.go:154`). **The deploy route never reads it.** Its field comment `// new apps use this by default` (`internal/settings/settings.go:453`) has never been true — an invariant with no test pinning it. **(3)** The first-tier backup follows the data onto the same disk (`backup/backup.go:324-334` → `systemDataPath`), so data and nearest copy share one device for a customer doing nothing wrong — the posture Tier 2 refuses outright at `tier2.go:329`. **(4)** „1 alkalmazás használja" on the Drives page counts only `Env["HDD_PATH"] == path` (`web/handlers.go:2118`), so it can never include the 40-class; it truthfully means *„1 of the apps that CAN use a drive does"*. | **NARROWED** — **PARTLY CLOSED 2026-08-21** — visibility shipped; **placement OPEN** | — | **Shipped tonight (visibility only, no placement change, nothing migrated):** the deploy page now states where the app's data will live before the button is pressed, naming the system drive for the 40-class and the selected drive for the 13. **⚠ RE-FRAMED 2026-08-22 — THE FOUR MEASUREMENTS STAND; TWO OF THE CONCLUSIONS DRAWN FROM THEM DO NOT.** (1) is a measurement and is correct, but "those apps get no storage field and no default" is not a deprivation: the architecture places **hot** data (DB/config/cache) on fast storage inside the guest and states that placement is **ENFORCED** (`documentation/architecture/01-topology-and-trust.md:150-152`). The 40 are all-hot apps; the 13 are the ones with **bulk** content, which belongs on an attached drive. There is no choice being denied. (3) **overstated one risk and understated a distinction.** Since R-165 the guest carries a small OS rootfs plus **ONE** data volume at `/var/lib/felhom`; `/var/lib/docker` and `/mnt/sys_drive` are two **binds of that same volume** (`felhom-agent/configs/build-golden.sh:29-40, 99`) — the `mp0`/`mp1` split assumed here was retired 2026-08-03. **Real risk:** a physical-disk failure loses the data and its first-tier copy together — which is what the off-site and whole-machine tiers exist for, and which is equally true of a drive-resident app whose unit sits beside its data by design. **Overstated risk:** a full data volume stopping the operating system — the OS rootfs is a separate volume and the capture floor refuses per app before exhaustion (`00-capability-map.md:94`), watched working 2026-08-21 with the volume at 99% and all 15 containers healthy. **The comparison to Tier 2's same-disk refusal (`tier2.go:329`) is withdrawn:** Tier 2 refuses a SECOND copy on the same disk; Tier 1's unit is meant to sit beside the data. **(2) and (4) are untouched and remain correct** — (2) is now filed on its own as **R-368** with its scope measured, and (4) needs no ruling: the count is honest and only easy to misread. **The specification for the rest is filed at `documentation/backlog/SPEC-app-data-placement-2026-08-21.md`** (corrected 2026-08-22, framing marked inline, measurements kept) and lists the five points a ruling must settle (compose-template vs controller, existing deployments, when the SSD is legitimately right, `IsDefault` must become true or go away *with a test*, and the Drives-page count). **An earlier recommendation to refuse deployment until a drive is registered was WITHDRAWN** — it assumed the customer had failed to choose; they had no choice to make. | **Viktor rules**, CC executes | -## Security & access — 16 rows (P1 1, P2 1, P3 10, P4 4) +## Security & access — 15 rows (P1 1, P2 1, P3 9, P4 4) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -201,17 +196,16 @@ stopping line that lies. | **R-132** | Security & access | P3 | **`curl -w '%{redirect_url}'` reconstructs the request URL WITH its basic-auth credential** — so a `-u ":$HUB_PW"` call that never put the password in a URL still printed it **Merged 2026-10-05 from R-350 (duplicate):** (1) 2026-08-20 occurrence: POST /configuration/artifacts answers 303; leak lives only in the CC transcript under ~/.claude/projects/ on DooPlex, not in git/evidence (checked then). (2) `-v` and `--libcurl` also re-render the credential, not only %{redirect_url}; confirm redirects with %{http_code} + follow-up GET. (3) Rotation path: hub /configuration form (current_password/new_password/confirm_password); DB override wins over ConfigMap (break-glass); CC can rotate file-to-file without printing (operator-present-one-time-secrets) if asked. | **WAITING-ON-OPERATOR** — **ACTION: rotate `HUB_PW`** **Folded R-580 2026-10-03** (the same `curl -w %{redirect_url}` credential echo, seen again 2026-09-18). | — | Happened on 2026-07-31 while red-proofing the R-120 gate: the hub operator password was written to the session transcript by the write-out format, not by the request. `-u` is safe; the *reporting* was not. Rule: read the redirect from `-D -` and grep `^Location:`, never `%{redirect_url}`, on any authenticated call. Rotate the hub password (`/configuration` → Login password; ConfigMap `auth.password_hash` is the reset path) and update `~/.config/credentials` | Viktor | | **R-137** | Security & access | P3 | **Cloudflare geo-WAF rules are zone-scoped and non-namespaced — four cross-tenant faults.** `globalRuleDesc = "[felhom-geo] Global"` (`waf.go:18`) is one literal description per ZONE; `appRuleDescPrefix` keys by app name with no customer (`waf.go:21`); `BuildGlobalExpression` has no positive hostname scoping (`waf.go:241`); `applyDiff` deletes every `[felhom-geo]` rule not in THIS box's desired set (`geosync.go:320`) | READY (M) — **blocks shared-zone onboarding** | — | With two customers in one zone: they overwrite each other's Global rule forever; one customer's country policy applies zone-wide; per-app rules collide by name; and disabling the feature for one (or the hub's `RemoveGeoRules`) wipes them all. Interim mitigation, no code: keep geo-restriction OFF for every shared-zone customer. Fix = namespace descriptions by `customer_id` + add `http.host ends_with ""` to both expressions — a TWO-REPO change (controller + hub `RemoveGeoRules`). Same audit §5.1 | CC | | **R-255** | Security & access | P3 | **The check that would catch a fourth secret-in-the-body covers 4 of 27 pages, and the cheap gate that covers all 36 templates is blind to the shape that actually shipped.** Filed 2026-08-08 while closing R-254, **because a partial guard reported as complete is worse than no guard — it stops the next person looking.** **Two nets, both measured.** **(1) `scripts/secret_in_markup_gate.py`** reads all 36 templates and convicts any `{{ … }}` naming a secret unless allowlisted with a reason. It catches `{{.RetrievalPassword}}` and `{{.InitialCreds.Password}}`, **and it catches a launder through a local variable** because the assignment itself names the secret (`{{$v := .InitialCreds.Password}}` is convicted — verified). **It is blind to a secret arriving under a NEUTRAL PAGE-DATA KEY** — `data["Tagline"] = creds.Password` then `{{.AppInfo.Tagline}}` passes it cleanly, also verified. **That is exactly the shape of R-254 site two** (`value="{{$val}}"` inside an `{{if eq .Type "secret"}}` branch), so the gate **would not have caught one of the three instances it was written for.** **(2) The runtime body assertion** — render the page and grep the response for a sentinel — catches every shape, including that one (demonstrated on the same planted leak the gate missed). But it needs each page's data to be constructible in a test, and **only 4 of 27 page templates have that today**: `settings_security`, `app_info`, `deploy`, `backups_restore` — the four that were touched by R-249/R-252/R-253/R-254 and therefore got their own tests. **The other 23 pages have no runtime coverage at all.** **What closing this needs, so the cost is not re-estimated:** a per-page data fixture for the remaining 23 (most need a wired `Server` — `stackMgr`, `backupMgr`, agent seams), then one table-driven test that renders each with a sentinel substituted for every string in its data and asserts the sentinel is absent. **That is real scaffolding, which is why it was NOT built inside R-254's session** rather than half-built and declared done. | **READY** — owner Viktor | — | — | operator | -| **R-338** | Security & access | P3 | **`demo-hp` is not on the R-50 island at all, and `operations/nodes.md` states that it is.** The page records both fleet boxes as island-migrated 2026-07-25. True of `felhom-pve`; **false of `demo-hp`**, whose `agent.json` has `listen_addr: 192.168.0.87:8443` — the customer LAN address — and **no `island_bridge`/`island_guest_addr` keys at all**, whose guest 9201 has `net0` only (no `eth1`), and whose `vmbr9` exists with **zero members**. The controller's `controller.yaml` points at the LAN address, so the box works; this is inventory drift, not breakage. **Two costs.** A session trusting the page addresses the wrong endpoint — that happened on 2026-08-18 and the resulting timeout was briefly read as a fault. And the agent's local API is **bound to the customer LAN on this box** rather than to a point-to-point island, which is the exposure R-50 was built to remove — so a documented security property is claimed for a box that does not have it **Checked from source 2026-10-05 (burn-down round 2):** nodes.md:86-88 still claims demo-hp is on the R-50 island (`local_api` on 169.254.253.1:8443/vmbr9, guest eth1). git blame: that claim dates from e6b5fa1e (2026-07-30); the 2026-09-21 edit bcdd5b20 re-read addresses but only reworded the lan_resolver clause -- the island claim was NOT re-verified after the reprovision. Agent config path /etc/felhom-agent/agent.json (felhom-agent cmd/felhom-agent/main.go:171), island keys island_bridge (internal/config/config.go:246). | **READY (S) — NEW 2026-08-18** | — | Decide which is true: migrate `demo-hp` to the island, or correct `nodes.md`. Leaving both is the one option that keeps the doc lying | Viktor decides; CC executes | | **R-616** | Security & access | P3 | **[P3-LOW] The catalog credentials are stored in PLAINTEXT in the box's catalog clone and are printed by an ordinary `git remote -v`.** FOUND 2026-09-21 on guest 9202 while pointing it at a private drill catalog. `Syncer.buildRepoURL` injects `username:token` into the HTTPS URL, and `git clone` persists that URL as the clone's `origin`, so `/catalog-cache/.git/config` holds the token in the clear and **any** diagnostic that prints the remote leaks it — which is what happened in this session's own transcript, and is the same shape as R-580 (`curl -w '%{redirect_url}'`). `maskRepoURL` exists and is used for the LOG lines, so the masking intent is already there; the stored remote is the half that was missed. **INERT ON THE FLEET TODAY** — the live catalog is public and `git.token` is empty on every real box — which is exactly why it should be fixed before it is not: the day the catalog goes private, every box carries a readable credential and every support session that runs `git remote -v` prints it. **Fix shape:** store the remote WITHOUT credentials and supply them per-fetch (a credential helper, `http.extraHeader`, or `GIT_ASKPASS`), and a test asserting the clone's stored `origin` contains no `@`. **Operator action from tonight, unrelated to the fix:** the Gitea `admin` token used for the drill repo was printed by that command and must be rotated. Evidence: `audits/update-night-2026-09-21/05-9202-follows-drill.txt` (redacted). | **READY — rank P3-LOW; owner: CC (controller); one operator action (rotate the Gitea admin token)** **2026-10-05 (burn-down night): FIXED on controller `main`** (`28a5203`; the catalog clone stores no credentials; the token is supplied per fetch; R-615's repo comparison ignores credentials on both sides, so a token never re-clones (`TestR616_TokenSetSameRepoNoRecloneOriginClean`) and a credentialed origin is cleaned at the next pull. The operator's Gitea admin token rotation (the row's second half) is still owed). Ships with the next controller release; close after delivery. **2026-10-06: DELIVERED** in controller v0.298.0 (the clone stores no credentials). Left: the operator's Gitea admin token rotation. | — | — | CC + operator | | **R-775** | Security & access | P3 | **[P2-MEDIUM] Grimmory: a stranger's 5 wrong sign-ins lock EVERY visitor out of the web login for 15 minutes — so Grimmory was not published.** MEASURED 2026-10-01 on 9202 (drill catalog, v3.4.1, through traefik): after 5 wrong tries for `admin` every further sign-in answered 429 — the household's right password AND a different name — and stayed 429 for 10+ minutes of retries. Read in the jar: `AuthRateLimitService` — Caffeine `expireAfterWrite(ofMinutes(15))`, `MAX_ATTEMPTS 5`, keys `login:ip:` and `login:user:`; Spring `forward-headers-strategy: native` takes the address from X-Forwarded-For, and behind the tunnel every visitor is the tunnel container's address (R-753) — the wger shape (R-752), with no setting to change it. Everything else in the checklist passed (bench + box step v3.4.1 → v3.5.0, gate by its own probe, OPDS through traefik); two smaller findings for the publishing session: on a reinstall over the first install's kept books, a new upload was saved to the drive but not added to the library (`box/grimmory/reinstall-c1.txt`, not investigated); and the remove + restore round trip (2.5) cannot be shown on 9202 for a drive app — its backup lives on the scratch drive, which is not a registered drive (R-756). The template waits in `audits/new-apps-2026-10-01/wip/grimmory/`. **Needs (operator):** (A) publish with a sentence on the page that wrong guesses by others can lock the login for 15 minutes (MEASURED: during the lock an e-reader's OPDS feed still answered 200 with its own login, wrong 401 — `box/grimmory/opds-under-lock.txt`), or (B) wait until the box passes each visitor's real address (R-753). `audits/new-apps-2026-10-01/box/grimmory/throttle.txt` **-- 2026-10-01 (evening):** option B's precondition SHIPPED (controller v0.286.1, R-753): Grimmory's Tomcat RemoteIpValve walks from the right and counts `172.16.0.0/12` as a proxy (READ in source, Spring Boot 4.1.1 — not yet measured with Grimmory's own lock), so `login:ip:` becomes per visitor; `login:user:` still lets a stranger lock the public name `admin` 15 min. A third route was spiked and passed: Grimmory behind the permanent family gate with its e-reader paths excepted (R-780). Recommendation: publish behind the family gate if R-780 is built; otherwise B with a measured 3.6. **UPDATE 2026-10-02 — NARROWED, Grimmory PUBLISHED behind the family gate:** a stranger cannot reach Grimmory's web sign-in at all (6 tries through the simulated tunnel: the gate's 401, then the household signs in 200 — `audits/family-gate-2026-10-02/A/items.txt`), and the e-reader exceptions keep Grimmory's own login. The 2.5 round trip now WORKS on 9202 (`audits/family-gate-2026-10-02/B/box/life.txt`). **What is left:** a family member past the gate can still lock a NAME (the admin's) for 15 minutes with 5 wrong tries — hard-coded in Grimmory; and the reinstall-over-kept-books finding (`new-apps-2026-10-01/box/grimmory/reinstall-c1.txt`) is still not investigated. | **WATCHING — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P2→P3: Grimmory is now behind the family gate; only a family member can still lock a name.** | — | — | CC | -| **R-782** | Security & access | P3 | **[P3-LOW] Two side observations of the R-753 sweep, inferred, not measured:** glance's seeded `glance.yml` has no `auth:` block (the dashboard is public to anyone with the address), and homepage's `/api/*` refuses a Host not in `HOMEPAGE_ALLOWED_HOSTS`, which the template does not set (widgets may 400). **Needs:** measure both on 9202; glance: decide whether a public link dashboard is intended (the setup gate does not cover it after setup). | **READY — rank P3-LOW; owner: CC (catalog)** **2026-10-06 night: homepage fixed on catalog branch `night-held-2026-10-06`** (`093e5ed`): `HOMEPAGE_ALLOWED_HOSTS=${SUBDOMAIN}.${DOMAIN}`; bench: `/api/services` 400 → 200. Glance measured: public (`GET /` 200 with no login, no auth block, `/login` 404) — whether it gets a login is the operator's question; the row stays open for it (`cat/R-782-red.txt`). | — | — | CC | +| **R-782** | Security & access | P3 | **[P3-LOW] Two side observations of the R-753 sweep, inferred, not measured:** glance's seeded `glance.yml` has no `auth:` block (the dashboard is public to anyone with the address), and homepage's `/api/*` refuses a Host not in `HOMEPAGE_ALLOWED_HOSTS`, which the template does not set (widgets may 400). **Needs:** measure both on 9202; glance: decide whether a public link dashboard is intended (the setup gate does not cover it after setup). | **READY** — **2026-10-10: the homepage half is on catalog `main` and live** (`templates/homepage/docker-compose.yml` line 20 `HOMEPAGE_ALLOWED_HOSTS=${SUBDOMAIN}.${DOMAIN}`). **Left: one operator question — does glance get a login?** It is on the 2026-10-10 decision sheet. **READY — rank P3-LOW; owner: CC (catalog)** **2026-10-06 night: homepage fixed on catalog branch `night-held-2026-10-06`** (`093e5ed`): `HOMEPAGE_ALLOWED_HOSTS=${SUBDOMAIN}.${DOMAIN}`; bench: `/api/services` 400 → 200. Glance measured: public (`GET /` 200 with no login, no auth block, `/login` 404) — whether it gets a login is the operator's question; the row stays open for it (`cat/R-782-red.txt`). | — | — | operator (the glance question), CC (the change) | | **R-831** | Security & access | P3 | **The Hetzner storage API token (`HETZNER_TOKEN`, the storage project's token in `Secret/storagebox`) was printed into the 2026-10-03 session transcript** — CC read the gitignored `manifests/storagebox.secret.yaml` and its redaction pattern missed the quoted value. It can create, reset and delete Storage Box sub-accounts. Not rotated by the operator's choice (decision 73). **Rotation, whenever chosen (3 steps):** create a new token in the storage project in the Hetzner console → patch `Secret/storagebox` key `HETZNER_TOKEN` in `felhom-system` and `kubectl rollout restart deployment/hub` → delete the old token in the console. Rule for sessions: never print a file that holds secrets — read the one field needed. | **WAITING-ON-OPERATOR — rotation is his call** **Not rotated by the operator's rulings (2026-10-04 „keep using the current one"; 2026-10-05 option B) — restated 2026-10-05 18:23; the steps stay here.** | — | rotate when chosen | operator | | **R-870** | Security & access | P3 | **Tester 1's two Cloudflare credentials — the zone API token (`infrastructure.cf_api_token`) and the tunnel token (`infrastructure.cf_tunnel_token`) of the hub's `customer_configs` row `tester-1` — were printed into the 2026-10-04 night session's transcript** (not into any file): a read-only query selected `substr(config_json,1,400)`, and both values sit in the first 400 characters. Tester 1 is CC's disposable test customer (`enkicsifelhom.hu`). **Not rotated, by the operator's ruling of 2026-10-05 06:49 (option B).** **Rotation, whenever chosen (3 steps):** in the Cloudflare dashboard create a new API token for the `enkicsifelhom.hu` zone with the same permissions and refresh the Tester 1 tunnel's token (Zero Trust → Networks → Tunnels → the tunnel → refresh token) → hub → Configs → `tester-1` → Edit → the two Cloudflare fields → Save, then confirm on the box that cloudflared reconnected (`docker ps` health `healthy`) → delete the old API token. Rule for sessions (as R-831): never select a whole config row — name the fields, and never `config_json` without `json_extract` of a non-secret field. | **WAITING-ON-OPERATOR — rotation is his call (ruled: not now)** **Not rotated by the operator's rulings (2026-10-04 „keep using the current one"; 2026-10-05 option B) — restated 2026-10-05 18:23; the steps stay here.** | — | rotate when chosen | operator | | **R-908** | Security & access | P3 | **An old Resend API key is still in the history of the `homelab-manifests` repository; it was replaced on 2026-06-29, but whether it is also revoked at Resend is unknown.** FOUND 2026-10-08 by the contact-mailer source search (`audits/mailer-source-2026-10-08/SEARCH.md`, „A side finding"): commit c9648cd (2026-02-05, „added mailer pod") put a key literal in the old `felhom-system/contact-mailer.yaml` comment; the file was deleted in ee93b50 but the history keeps it. Compared by hash only, never printed: it is NOT today's key (`Secret/resend-api`, rotated 2026-06-29, felhom.eu feea0606). If the old key still works at Resend, anyone with read access to that repository can send mail as felhom.eu. | **OPEN — owner: operator** | — | In the Resend console, check that the old key is revoked (revoke it if not); rewriting the repository's history is NOT needed once it is revoked | operator | | **R-525** | Security & access | P4 | **[P3-LOW] FileBrowser has its own login; putting it behind the dashboard session (traefik forwardAuth or Quantum proxy auth) is a new mechanism nobody has measured.** Filed 2026-09-15 by the P1-fixes task (B.5). R-513 closed the default-password hole with a generated password; a household still has two logins. **What it needs:** a spike on a scratch guest — forwardAuth to the controller session, and what FileBrowser Quantum does with a trusted header. | **READY — rank P3-LOW; owner: CC (spike)** **Re-ranked 2026-10-03: P3->P4: comfort feature needing a new unmeasured mechanism; the default password hole is closed.** | — | — | CC | | **R-925** | Security & access | P1 | **`manifests/felhom.secret.yaml` carries REAL secret values, and the Gitea repo that holds it is readable by anyone on the internet with no login.** FOUND 2026-10-09 by a background security review of the legal-pages commit, which flagged the served `` comments; chasing what those comments pointed at found this instead. **MEASURED, in this order:** (1) `scripts/manifest_bearer_gate.py` itself prints `manifests/felhom.secret.yaml:39 KNOWN-BACKLOG committed secret 65cee3c4…7a86`; (2) the file holds **live-shaped values, not placeholders** — `SECRET_KEY` (69 chars), `SUPERUSER_EMAIL`/`SUPERUSER_PASSWORD` (18), Umami `APP_SECRET` (66) and `POSTGRES_PASSWORD` (34), and a `username`/`password` pair (18); values were never printed, only measured by length; (3) `gitea.dooplex.hu` resolves to **37.191.56.193**, the same public address as the website; (4) an **anonymous** `curl` returns HTTP 200 and 1686 bytes for that exact file, and 200 for `hub/internal/store/store.go` and `manifests/hub.yaml`; (5) **the off-network control**, which is what makes (4) mean anything — fetched from outside the operator's network entirely: a real path returns the file's first line, a nonsense path returns 404, so the 200 is genuine anonymous read from the internet and not a LAN-only ACL. **This contradicts the project's own written model**: `documentation/runbooks/secrets.md:3-4` says *“Secret values are never committed to git. The manifests in `manifests/` carry only placeholders + comments that point here.”* That promise is false today, which is the P1 wording in this register's own scale — see the ranking note below. The same runbook already states the rule that governs the fix: *“The git-history copy stays alive until the value is ROTATED — de-git alone kills nothing.”* **Known neighbours, none of which cover this:** R-887 records that an outside crawler walks the public Gitea pages and leaves *“Gitea's exposure to the crawler”* as the operator's undone item; R-580 still owes a Gitea admin token rotation. Neither says a secret file is world-readable. **Why P2 and not P1, stated so the operator can overrule it:** the exposed credentials guard **analytics and an undeployed healthchecks instance, not household data** — measured: `umami-db` is a **ClusterIP** service on 5432 with no external IP, so the Postgres password is not reachable from the internet; the realistic harm is session forgery against the public `stats.felhom.eu` via `APP_SECRET`, and reuse of those passwords anywhere else. No customer box, hub token or escrow key is in this file. **CC did NOT change anything**: making the repo private could break the public day-0 path (the installer is fetched from a public tag, R-110), and rotation plus repository visibility are operator decisions on production infrastructure. **PARTLY FIXED 2026-10-09, and RE-RANKED P2 -> P1 on what it turned out to be.** The exposed value was not an analytics password: it was the **Gitea `admin` account password** (`is_admin: true`; `/api/v1/admin/users` answered 200), published in a repo `gitea.dooplex.hu` serves anonymously to the internet. That is push access to every repo — including the one whose `website/` is git-synced live and whose `scripts/` is published by tag to **every new box installer** (R-110), i.e. a supply-chain path onto customer hardware. My first ranking said P2 because I had only measured the analytics blast radius; the credential test is what corrected it. **DONE (CC, operator-instructed):** (1) `umami-config` `APP_SECRET` + `POSTGRES_PASSWORD` rotated — the password changed **inside Postgres** too (`ALTER USER`), because the env var is only read at first init; verified by a real beacon returning 200 with a bogus-site-id control still returning 400. (2) `healthchecks-config` `SECRET_KEY` + `SUPERUSER_PASSWORD` rotated (nothing consumes them — there is no healthchecks Deployment). (3) `gitea-creds` no longer holds the admin password at all: it holds a **scoped token** (`read:package` + `read:repository`). **Both scopes are load-bearing and the second was found by breaking it** — a package-only token made the hub log `Template fetch: unexpected status 403`, because the template fetcher reads a raw file out of the `felhom-controller` repo. (4) The Gitea **admin password** was changed and `gitea-system/gitea-admin` updated; **verified: new password 200, old published password 401 — the leak is dead.** (5) The file is removed from git and `.gitignore`'s `*secret*` now applies to it; `manifest_bearer_gate.py`'s `KNOWN_BACKLOG` carve-out is gone, red-proofed with a decoy (exit 1 with, 0 without). **AN INCIDENT CAUSED BY THE FIX, recorded because it is the useful part:** the `rollout restart` needed to pick up the new umami secret put umami into **CrashLoopBackOff and took `stats.felhom.eu` down (503)**. Cause was not the rotation — at `memory: 512Mi` the pod runs for months but **cannot restart**, because startup (Prisma + Next.js) peaks over the limit and is OOMKilled (exit 137). Raised to 1Gi, **in the manifest, not just live** (the `.claude/rules/manifests.md` rule: a bare `kubectl set` is reverted by the next sync and the fix silently disappears). Service restored and verified. New values are in a 0600 file on DooPlex (`~/rotated-secrets-2026-10-09.txt`), never echoed; the operator moves them to the password manager and deletes it. **WHAT REMAINS, and it is bigger than what was fixed:** that one password is still the admin password in about **12 other namespaces** — `nextcloud`, `paperless`, `bookstack` (a **database root** password), `tandoor`, `calibre`, `adventurelog`, `gokapi`, `qbittorrent` (x2), `servarr`, `homepage` (x2). Rotating Gitea does not touch them: each is its own login and each is still the string that was published. **Also owed:** a Gitea token for user `kisfenyo` sits in plaintext in the `origin` URL of the local `homelab-manifests` clone (`.git/config`); CC printed it to a session transcript while investigating, so it should be rotated regardless — this is R-580's shape (store the remote without credentials). **Not a finding:** `homelab-manifests` itself is private (404 anonymously) and does not contain the password; ArgoCD's repo credential is a separate token, so the rotation did not touch it. | **NARROWED 2026-10-09 — the felhom.eu half is DONE and verified (Gitea admin password rotated, old one now 401; umami + healthchecks rotated; file de-gitted; gate carve-out removed). What remains is the ~12 reused logins elsewhere in the homelab and the repo-visibility decision; owner: operator** **2026-10-10 (release session), two consequences measured:** (a) DooPlex's own Docker login for `gitea.dooplex.hu` (`~/.docker/config.json`) still holds the OLD admin password — the registry token endpoint answers 401 for it and 200 for the current one — so every image push from DooPlex fails `unauthorized` until it is refreshed; the 0.145.0/0.305.0 pushes used a one-off login in a scratch `DOCKER_CONFIG`, shredded after. (b) The `felhom` ArgoCD app reads OutOfSync for `Secret/gitea-creds`, `Secret/healthchecks-config`, `Secret/umami-config` and `Deployment/umami`: a whole-app sync would push git's (de-gitted) view over the rotated live Secrets — the hub deploy synced ONLY `Deployment/hub` (`audits/release-2026-10-10/hub-0.145.0-deploy.txt`). Refreshing the DooPlex login is a DooPlex change (operator's word). | — | **OPERATOR RULED 2026-10-09: (a) he rotates the remaining services himself — CC's scope stopped at the Felhom boundary; (b) the repo STAYS anonymously readable for now**, because making it private breaks the website git-sync and the installer tag fetch, which both clone with no credentials (R-110). The standing consequence of (b): no secret may ever enter this repo again, which `manifest_bearer_gate.py` now enforces with no exemption; and if (b) is reversed, the 32 `` comments on /adatkezeles must be stripped in the same change. **The exact checklist of the 12 remaining secrets** (namespace / secret / key, re-measured after the rotation, with the two traps that bite — a DB password is not changed by editing the Secret, and a pod that has run for months may not restart) is in `documentation/runbooks/secrets.md`. Operator: **(1)** work that checklist, `bookstack-db/root-password` first because it is a database root password and every one of these hosts answers on the public internet; (`bookstack` first — it is a database root password); **(2)** rotate the `kisfenyo` Gitea token embedded in the local `homelab-manifests` remote URL, and store the remote without credentials; **(3)** decide whether `gitea.dooplex.hu` should answer anonymously at all — remembering the website git-sync clones it with no credentials and the installer is fetched from a public tag, so making it private breaks both unless they get credentials first, and the 32 `` comments on /adatkezeles become a map of it; **(4)** move `~/rotated-secrets-2026-10-09.txt` into the password manager and delete it. If nothing is done: the published password keeps opening a dozen services, even though Gitea itself is now safe | operator | | **R-779** | Security & access | P4 | **[P3-LOW] Part A's "two outside addresses seen as two" is proven through the simulated tunnel only; on the REAL tunnel the second outside address (ep0, one request allowed) was refused by Cloudflare's edge with 403 and never reached the box.** Measured 2026-10-01 19:51 UTC (`audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt`): no log line on demo-hp; demo-hp's box has no geo restriction in its settings, so a Cloudflare ZONE rule (country or bot, not read) refused a German datacenter address. DooPlex's own address on the real tunnel was seen as itself. **Needs:** one sign-in from a second Hungarian address (the operator's phone off wifi) while DooPlex is locked out — 2 minutes; and say which Cloudflare rule refused ep0. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (a phone), CC reads the logs** **Re-ranked 2026-10-03: P3→P4: a proof gap on the real tunnel; operator-only follow-up.** | — | — | CC + operator | -| **R-904** | Security & access | P4 | **Cloudflare can read every household's app traffic; replacing it with our own relay is a later item.** Facts (reviewer discussion 2026-10-08; `01`): app traffic and the dashboard reach the box through the Cloudflare Tunnel (`01` §5 trust table, rows end-user ↔ apps and customer ↔ controller UI; §7), and the tunnel's public end is Cloudflare's edge, where TLS ends — so Cloudflare can technically read that traffic (the FAQ says so since 2026-10-08, R-900; „TLS ends at the edge" is not written in `01` — add it there). What Cloudflare gives today, free: inbound reach with no router setup, the CGNAT answer (`01` §4, §7); certificates (`01` §7, the free tier covers one level below a zone); the geo-WAF the hub enforces (`01` §5 last row, §7); flood protection (not in `01`). The alternative named: our own EU relay over WireGuard with TLS passthrough by SNI, certificates on the box, the geo-block on the relay. Its costs: one more machine the operator keeps up, and a single point of reach for every box; weaker flood protection; about a week of work after a spike. Operator ruling 2026-10-08 09:07 (`09` §3 decision 184): a later item. | **DEFERRED — after the first customers (operator ruling 2026-10-08 09:07)** | — | A spike after the first customers (the relay's reach, cost and flood behaviour, measured) | operator | +| **R-904** | Security & access | P4 | **Cloudflare can read every household's app traffic; replacing it with our own relay is a later item.** Facts (reviewer discussion 2026-10-08; `01`): app traffic and the dashboard reach the box through the Cloudflare Tunnel (`01` §5 trust table, rows end-user ↔ apps and customer ↔ controller UI; §7), and the tunnel's public end is Cloudflare's edge, where TLS ends — so Cloudflare can technically read that traffic (the FAQ says so since 2026-10-08, R-900; „TLS ends at the edge" is not written in `01` — add it there). What Cloudflare gives today, free: inbound reach with no router setup, the CGNAT answer (`01` §4, §7); certificates (`01` §7, the free tier covers one level below a zone); the geo-WAF the hub enforces (`01` §5 last row, §7); flood protection (not in `01`). The alternative named: our own EU relay over WireGuard with TLS passthrough by SNI, certificates on the box, the geo-block on the relay. Its costs: one more machine the operator keeps up, and a single point of reach for every box; weaker flood protection; about a week of work after a spike. Operator ruling 2026-10-08 09:07 (`09` §3 decision 184): a later item. | **DEFERRED** — **2026-10-10: the asked sentence is in `01` §7** („TLS ends at Cloudflare's edge …”); the spike stays deferred. **DEFERRED — after the first customers (operator ruling 2026-10-08 09:07)** | — | A spike after the first customers (the relay's reach, cost and flood behaviour, measured) | operator | | **R-913** | Security & access | P4 | **The Cloudflare token check (R-138, decision 190) reads what a token can SEE, not what it can WRITE.** FOUND 2026-10-08 by the security review of the R-138 build: `GET /zones` lists zones the token can read; a hand-built token with Zone:Read on the customer's zone and DNS:Edit on ALL zones would pass the check and could still change every household's DNS. The token wizard's single „Specific zone" scope does not build such a token, and a customer token cannot read its own policies (that needs „API Tokens Read"). Written as a limit in `01` §7. **Options:** (a) the operator mints every customer token from one recipe and the hub checks nothing more; (b) the hub mints the token itself with the operator's account token (one zone, DNS:Edit) — a new privileged credential on the hub; (c) a negative probe: with the pasted token, try to READ the DNS records of another customer's zone by its id (the hub knows the ids) and refuse on success — catches the read side only. | **OPEN — needs the operator** (a is free today; b is a design) | operator: which of a/b/c | Pick a/b/c; if nothing: the check stands as built and the limit stays written in `01` §7 | operator | ## Box system & updates — 6 rows (P2 1, P3 5) @@ -219,17 +213,17 @@ stopping line that lies. | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-836** | Box system & updates | P2 | **A new host kernel that hangs before userspace stays the GRUB default: `--next-boot` is not a one-shot on these hosts.** MEASURED 2026-10-04 on demo-hp (operator's word, 2 reboots): both demo hosts boot UEFI + GRUB without proxmox-boot-tool ESPs; installing a kernel makes it the default at once; `kernel pin --next-boot` writes an ordinary `GRUB_DEFAULT`, and `proxmox-boot-cleanup.service` clears it only after a boot reaches userspace. With the old kernel pinned FIRST, the fallback after a good boot worked (new 60 s, old 76 s). READ FROM THE CODE, not measured: a hang leaves the new kernel default on every power cycle. Only `softdog` runs (useless before userspace); demo-hp's `sp5100_tco` ships unloaded, untested. Fix direction for the slow lane: GRUB's own one-shot (`GRUB_DEFAULT=saved` + `grub-reboot`) with the old kernel saved — to be measured, including Secure Boot (ON on demo-hp). `audits/os-updates-spike-2026-10-04/partH/` | **NARROWED — 2026-10-09 read-back (`audits/kernel-night-2026-10-08/readback/`): demo-felhom took the told 7.0.14-22 (staged 04:44:42, restart 04:44:46, healthy 39 s after the agent started, now the default; apps down ~70 s). demo-hp took NO kernel: its Proxmox step was refused R6 — `proxmox-secure-boot-support` pulled `shim-signed-common` — and the kernel step is skipped after a refused Proxmox step; the household had been told „tonight" (mail 2 of 3). Fixed in agent 0.154.0 (the meta-package joins the boot-chain lane, red-proved), delivered 07:31. The approval button stays hidden until demo-hp boots the same kernel; meanwhile 7.0.14-23 appeared (demo-felhom „due 23, not told yet").** **NARROWED 2026-10-04 (os-host-lane Part E, operator's word before each of 2 reboots) — GRUB's own one-shot is NOT a one-shot here either.** `GRUB_DEFAULT=saved` (old kernel saved) + `grub-reboot `: boot 1 → new kernel, **Secure Boot ON and fine**; but GRUB could not clear `next_entry` (`grub-reboot` itself warns: *environment block on lvm device … will remain the default until manually cleared*; `/boot` is ext4 on LVM `pve-root`), so boot 2 (no command) → **the new kernel again**. `kernel.panic = 0`: a panic leaves the host stopped (R-851). `sp5100_tco` LOADS and answers (`SP5100 TCO timer`, 60 s, inactive, nowayout 0; read from sysfs, never opened, unloaded) — a hardware watchdog exists on demo-hp, but nothing arms it before userspace. demo-hp left on 7.0.14-20 with that as the saved default. **LEFT (fix direction):** a GRUB env block GRUB can write (on the ESP, vfat) or a userspace "boot good" step that rewrites the default, plus arming `sp5100_tco`; to be measured before the kernel slow lane. `audits/os-host-lane-2026-10-04/partE/` **READY — owner: CC + operator (reboots).** **2026-10-07 07:58: raised P3 → P2 (`09` §3 decision 164 — the kernel lane before the first paying customer); the spike runs with the operator present.** **2026-10-07 SPIKE DONE (operator present; 24 reboots, 2 power cycles; `audits/kernel-spike-2026-10-07/DESIGN-kernel-lane.md`).** On Tester 1 (VM), demo-felhom and demo-hp (Secure Boot on): **a one-shot flag in a GRUB env block on the ESP (vfat) WORKS** — new kernel once, then the old one; a panicking new kernel (`panic=10`) falls back with no person. **UEFI `BootNext` WORKS too.** **A hardware watchdog armed by systemd during the reboot FAILS on all three** (i6300ESB, Intel TCO, AMD SP5100): the reset clears the timer, so a FROZEN new kernel needs a power cycle. Pick: the ESP flag + lockup-to-panic kernel options (unmeasured). Two operator questions in the design (night restarts and the household; a freeze needs a person). Boxes left clean on a healthily booted default. **2026-10-07 14:43 — `09` §3 decision 172: the kernel lane is YES — option A (the ESP one-shot flag) with option C (lockup → panic) on the one-shot entry; a box may restart at night; the household is mailed the day before (what to do if it is not back: unplug, plug back in); each kernel set is operator-approved after ring 0; a frozen new kernel needing a power cycle is ACCEPTED for the first customers. BUILD STARTED 2026-10-07 (the kernel-lane arc).** **2026-10-07 evening: BUILT and proven by hand** — agent v0.152.0 + hub v0.143.0 (`11` §5.11), delivered to demo-hp, demo-felhom and Tester 1. Tester 1 (`audits/kernel-lane-2026-10-07/E/RESULT.md`): a forced panic on the one-shot → back on the old kernel by itself (fell_back, 0 unclean boots counted); a held guest → judged 20 min → ONE self-revert → old kernel; a healthy boot → 7.0.14-22 the default. The household mail went out for demo-felhom 18:12 (Gmail). Option C UNMEASURED (no test_lockup module). **WHERE IT STOPPED (next session):** the ring-0 NIGHT run (Part E3) — tonight demo-felhom refuses the told 7.0.14-20 (R-898, safe); 7.0.14-22 is due on both demo boxes for the night 2026-10-08→09 (mail 2026-10-08 daytime); read back the morning of 2026-10-09; then approve the set on the System page and deliver to the Tester 1 box as ring 1 by a signed os_kernel_step (Part E4 — Tester 1 already runs 7.0.14-22, so the ring-1 proof needs the NEXT kernel). **2026-10-07 evening (decision 176): the night run moved to TONIGHT 7→8.** Pre-night fixes delivered (agent v0.153.0 — ring 0 stages exactly the told kernel, R-898 closed; controller v0.303.0 — the first capture after a boot waits for the drive, R-897 closed; hub v0.143.1 — Reply-To on household mails). Armed at 19:05: demo-felhom due 7.0.14-20 (household told 18:12), demo-hp due 7.0.14-22 (told 18:38; demo-hp's day-old apt lists refreshed by a report-only pass with its updates switched off for ~1 min). **WHERE IT STOPPED: read back the night on 2026-10-08 morning** (each box on its kernel as default, the apps back, minutes down, no false backup failure), then approve the set (the two boxes ran DIFFERENT kernels — the button waits until both boot the same one) and Part E4. **2026-10-08 read-back (`audits/kernel-night-2026-10-07/readback/`): demo-felhom PASSED its first real kernel night** — whole-guest backup 04:35, OS + Proxmox steps 04:37–04:38, kernel staged 04:39:04 as EXACTLY the told 7.0.14-20 (not the newer 22 — R-898's fix held), restart 04:39:08, new kernel judged healthy 04:40:38 (38 s after the agent started), now the default; apps away ~1.5 min; no operator alarm, no false backup failure (demo-felhom has no drive apps, so R-897's fix was not exercised there); crash guard 0. **demo-hp took NO step** — no whole-guest backup that night (R-899): it stays on 7.0.14-20, still due 7.0.14-22; a new household mail is allowed after 14:38 today, so it should run the night 2026-10-08→09. Reply-To proven: the operator's reply to the test mail went to admin@felhom.eu (Gmail 2026-10-07 18:29 UTC). **WHERE IT STOPPED:** read back demo-hp the morning of 2026-10-09; the approval button waits until both demo boxes booted the SAME kernel (now 20 vs due 22) — likely a step to 22 on demo-felhom later; then Part E4. | — | — | CC | -| **R-899** | Box system & updates | P3 | **A daytime whole-guest backup moves the 24 h cadence past the night window, so the next night has no whole-guest backup — and with it no OS leg and no kernel step.** SEEN 2026-10-08: demo-hp's last whole-guest backup was a morning press at 2026-10-07 08:49:53; due again 2026-10-08 08:49, after the window [04:30, 08:30) closed → no backup, no night leg; the household had been told „tonight" (18:38) and nothing happened (harmless: no change). The kernel lane recovers by itself (a new mail after 20 h, step the next night, inside the 3-mail limit), but every daytime press costs one night. Fix direction (a design, not taken): the cadence counts nights, not 24 h (due when the last whole-guest backup is older than ~20 h at the window's start), or a press does not reset the night's cadence. `audits/kernel-night-2026-10-07/readback/demo-hp-why-no-step.txt` | **VERIFY — 2026-10-09: DELIVERED (controller 0.304.0 + agent 0.154.0 on demo-hp, demo-felhom, Tester 1). Closes after the first night that follows a daytime press.** **VERIFY — fixed on main 2026-10-08, ships with tomorrow's controller + agent releases** (operator ruling 2026-10-08 07:12, option A, `09` §3 decision 177: every night takes its own). Controller `6d07ca2`: a durable ledger (last successful press / last successful scheduled run per tier); a tier the agent calls „not due" because of a press is still due inside tonight's window, never by the safety valve (`TestR899_*`, red-proved). Agent `4c69c25`: a press arrives as `trigger=manual` and runs no OS leg; the after-boot kernel reports carry the saved ring instead of „ring 1". The household mail case („tonight" for a night whose backup cannot start because of a press) is removed by the rule; no hub change. `07` §6.1. | — | Release controller + agent tomorrow; read back the first night after a daytime press; then close | CC | +| **R-899** | Box system & updates | P3 | **A daytime whole-guest backup moves the 24 h cadence past the night window, so the next night has no whole-guest backup — and with it no OS leg and no kernel step.** SEEN 2026-10-08: demo-hp's last whole-guest backup was a morning press at 2026-10-07 08:49:53; due again 2026-10-08 08:49, after the window [04:30, 08:30) closed → no backup, no night leg; the household had been told „tonight" (18:38) and nothing happened (harmless: no change). The kernel lane recovers by itself (a new mail after 20 h, step the next night, inside the 3-mail limit), but every daytime press costs one night. Fix direction (a design, not taken): the cadence counts nights, not 24 h (due when the last whole-guest backup is older than ~20 h at the window's start), or a press does not reset the night's cadence. `audits/kernel-night-2026-10-07/readback/demo-hp-why-no-step.txt` | **VERIFY** — **2026-10-10: the condition is SET UP — a daytime press on Tester 1 at 12:49Z** (`POST /api/guest-backup/trigger` → 200; the box's ledger then read `press_ok 2026-10-10T12:51:27Z`, `scheduled_ok 2026-10-10T02:52:53Z`). Read back on 2026-10-11 after 08:30: `scheduled_ok` must be a night-window time of 2026-10-11; then close (dated check). **VERIFY — 2026-10-09: DELIVERED (controller 0.304.0 + agent 0.154.0 on demo-hp, demo-felhom, Tester 1). Closes after the first night that follows a daytime press.** **VERIFY — fixed on main 2026-10-08, ships with tomorrow's controller + agent releases** (operator ruling 2026-10-08 07:12, option A, `09` §3 decision 177: every night takes its own). Controller `6d07ca2`: a durable ledger (last successful press / last successful scheduled run per tier); a tier the agent calls „not due" because of a press is still due inside tonight's window, never by the safety valve (`TestR899_*`, red-proved). Agent `4c69c25`: a press arrives as `trigger=manual` and runs no OS leg; the after-boot kernel reports carry the saved ring instead of „ring 1". The household mail case („tonight" for a night whose backup cannot start because of a press) is removed by the rule; no hub change. `07` §6.1. | — | 2026-10-11 after 08:30: read `whole-guest-ledger.json` on Tester 1 (scheduled_ok in tonight's window) + the agent journal; close | CC | | **R-862** | Box system & updates | P3 | **Tester 2 cannot take the config bundle until one by-hand bootstrap is done: its `felhom-os-apply` (agent 0.142.0) predates the bundle mode, and no signed job can write a root file on it.** FOUND 2026-10-04 (R-840 build, Part C): the route reaches every box installed from installer 1.31.0 on, and the demo boxes (bootstrapped by CC); Tester 2 has every root file of agent 0.142.0 (installer 1.30.0) and lacks only the R-858 wrapper fix, which matters only for a Docker step it gets solely from a signed job. CC has no route to Tester 2 (its door admits only the operator's WireGuard peer; `felhom-op` cannot become root). The steps: `runbooks/config-bundle.md` "Tester 2". Then CC signs the bundle and reads it back. | **WAITING-ON-OPERATOR** — the operator said (2026-10-04 ~19:05) he will try through his tunnel **2026-10-07 07:58 (`09` §3 decision 170): the operator believes Tester 2 never set up the off-site backup (no escrow); the laptop may stay off for days; the tester plans to add an HDD.** **2026-10-07 (Part H, read only):** in Tester 2's last 10 notifications (2026-10-05 09:16 → 2026-10-07 06:58) the household got 0 mails, the operator 10 (host_down ×9, expected_dbdump_missed ×1); control: the household channel shows on the other three customers. So the household is not mailed daily while the box is off; no row (`audits/day-2026-10-07/H/`). | the operator's WireGuard tunnel | the operator runs the three bootstrap commands; CC sends the bundle | operator | | **R-78** | Box system & updates | P3 | **`local_api` authority ruling — auto-reconcile vs detect-only** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-25, size M, roadmap state `idea (deferred OUT of R-77 on purpose)`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. **An OWED OPERATOR DECISION, not a defect — moved because an owed ruling hidden among feature ideas is the shape this session exists to remove.** | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** | — | R-77 ships detection because the fix is genuinely undecided, and **both directions can lose customer-visible function**. **Direction 1 (today):** `controller.yaml` wins and drift is silent → the 2026-07-25 island migration blinded the whole fleet's control plane for 17.5 h (drive gate, guest-reboot recovery, quiesce/backup all degrade). R-77 makes that loud but does not stop it recurring. **Direction 2 (`bootstrap.json` wins, auto-reconcile on boot):** a guest whose `controller.yaml` is CORRECT and whose `bootstrap.json` is stale — a half-completed re-provision, a hand-repaired guest, a setup-wizard box — gets a **working channel clobbered on the next restart**, fleet-wide and silently, during a routine deploy. That is not obviously better than the bug. Needs a spike: which writer is authoritative per field (endpoint vs fingerprint vs token — `mergeLocalAPI` replaces the whole block, so they cannot be reconciled independently today), whether the agent side should stamp a generation/mtime so 'newer wins' is even expressible, and whether reconcile should require an operator ack. Until then the drift alert plus a manual edit is the supported path. | CC | | **R-242** | Box system & updates | P3 | **A controller release that changes customer-visible behaviour is not delivered until a golden carries it — and nothing enforces that.** R-239 is the symptom; this is the mechanism, recorded **2026-08-07** and deliberately **NOT built** (the task that found it scoped it as a record-only item). **Two releases went out without a golden and the gap was invisible until a walk measured it from the customer's side**: v0.204.0 (R-237) and v0.205.0 (R-234) were written, tested, pushed, and CHANGELOG'd, and every one of those steps passed while a machine installed that night received neither. The register said CLOSED; the fleet said otherwise. **Nothing in the release path knows a golden exists.** The version bump, the image push, the CHANGELOG entry and the register closure are all repo-local; the manifest's `golden_version` is edited by a separate operator act, in a different repo, with no link back. **Proposed shapes, cheapest first — the choice is the operator's and is not taken here.** (a) **A release-path checklist step** — one line in the controller's end-of-session checklist: *a release that changes customer-visible behaviour is not finished until a golden carries it or a register row says why not.* Costs nothing, catches nothing mechanically. (b) **A gate in `repo_gates.py`** comparing the manifest's `golden_version` against the newest released controller and FAILING (or warning) past a tolerance of one minor. Mechanical, runs on every push, and would have fired the morning after v0.204.0. (c) **A hub-side checker** — the hub already knows every box's running controller version from `/hosts` and the vouched golden from the manifest; a periodic comparison against the newest published image would catch drift the repo cannot see, including a vouch that was made and then rolled back. **Earliest catch: (b).** It fires on the push that creates the gap, before any box is installed, and it needs no live fleet. **(c) catches strictly more but only after boxes exist.** (a) is worth doing regardless because it is free. **Not built. No gate was written this session.** **⚠ IT RECURRED WITHIN A DAY, WHICH IS THE ARGUMENT FOR BUILDING IT.** Controller **v0.206.0** shipped the R-241 fixes on 2026-08-07 while the vouched golden still carried **0.205.0** — so a machine installed on the morning of 2026-08-08 would have received neither. Third occurrence of the shape in three days (R-111/R-115/R-120 are the older family). **✅ SHAPE (b) BUILT 2026-08-08 — `scripts/golden_currency_gate.py`, registered in `repo_gates.py` as gate 7.** **It was shown FAILING against that exact state before anything was baked**, which is its red-proof and the reason its own introducing push needed `--no-verify` (stated in the session report rather than worked around): `newest released controller : 0.206.0 / newest golden baked : 0.205.0 → CONVICTED`. **IT IS `--fast`, AND THAT FORCED ITS DESIGN:** both `.githooks/pre-push` AND CI run `repo_gates.py --fast`, so a non-fast gate would run in NEITHER — the R-29 census failure this runner exists to end. **THEREFORE IT CHECKS THE BAKE, NOT THE VOUCH**, because the vouched version lives only in the hub's `hub_settings` with no copy in git, and putting a copy there would create a second source of truth that can drift — a green gate over a false claim being the worst outcome available. **A bake without a vouch still passes: that half is NOT closed and stays on this row.** It also compares versions rather than behaviour, so a release changing nothing customer-visible trips it too — accepted deliberately, because judging that by hand is what failed three times and the cost of a false trip is one bake; a waiver belongs here, never in a habit of bypassing. **VOUCHED 2026-08-08 with the operator's approval** — golden `0.206.0` / sha `c85230b4…108e`; `agent_version` and `min_agent` both stayed `0.127.0`, and `wrapper_sha256` was carried through explicitly because the handler clears it when omitted. **The gate was CONVICTED before the bake and OK after it** — red→green on the same command, which is its proof that it measures something real. **⚠ THE GATE FIRED FOR REAL, 2026-08-08 — and it was right.** Controller **v0.207.0** (R-249/R-252/R-253) is released, tested and pushed, and **no golden carries it** — the newest bake is 0.206.0 — so `golden_currency_gate.py` FAILED, saying exactly the true thing: *a machine installed right now would receive v0.206.0*. **The `felhom.eu` push therefore used `git push --no-verify`, declared here, in the commit message and in the session report.** A bypass and NOT a waiver, deliberately: the gate offers a waiver only for a release that *deliberately needs no golden*, and this one needs one. **Owed: bake golden 0.207.0 and vouch it** (`RUNBOOK-manual-build.md` §4.1; the vouch is a three-field change). **This row's own remaining half is unchanged — nothing gates the VOUCH itself.** **✅ THE OWED BAKE IS DONE, SAME DAY — golden 0.207.0 baked, published, round-trip verified and VOUCHED (2026-08-08).** The gate went from red to **green**, and the `--no-verify` bypass declared above is now historical rather than standing. **Round trip is the evidence, not the build log:** the published bytes were downloaded back — 656 879 192 B, sha256 `20ec9602…22995`, both identical to what the bake reported — and **`./etc/felhom-controller-image` read OUT of the downloaded archive says `felhom-controller:0.207.0`**, which is the delivered artifact naming the controller it will start. **The vouch was a three-field change with all three checked deliberately** (`MinAgent 0.127.0` read from the golden's controller CHANGELOG header, not assumed; `agent_version` already ≥ it; `min_agent` not above `agent_version`, so not the R-216 shape) and verified by **re-reading the manifest rather than trusting the flash**. **This row's remaining half is UNCHANGED and is the whole of what is still open: nothing gates the VOUCH itself** — the currency gate's own docstring says it checks the bake, so a baked-but-unvouched golden still passes it silently. Evidence: `tests/golden-0.207.0-2026-08-08/`. **⚠ RED AGAIN, 2026-08-08 (second time in two days) — controller v0.208.0 (R-254) is released and the vouched golden is 0.207.0.** `golden_currency_gate.py` FAILS, correctly: a machine installed right now receives 0.207.0 and none of today's fixes. **The `felhom.eu` push used `git push --no-verify`, declared in the commit, the CHANGELOG and the session report** — **a bypass, not a waiver**, on the same reasoning as yesterday: the gate offers a waiver only for a release that *deliberately needs no golden*, and this one needs one. **Owed: bake golden 0.208.0 and vouch it** (`RUNBOOK-manual-build.md` §4.1; three-field change, `MinAgent 0.127.0` unchanged). **Note the cadence this is establishing: two releases, two bakes owed within 24 h.** That is the argument for this row's OTHER half — nothing gates the vouch, so the only thing standing between a release and an undelivered fleet is somebody remembering. **⚠ RED AGAIN, 2026-08-30 — controller v0.224.0 (R-330) and v0.225.0 (R-331) are released and the newest golden carries 0.223.0.** `golden_currency_gate.py` FAILS, correctly: a machine installed right now receives 0.223.0 and neither of today's fixes. **The `felhom.eu` push used `git push --no-verify`, declared in the commit message, in `hub/CHANGELOG.md` and in `REPORT.md` — a BYPASS, not a waiver**, on the same reasoning as the two 2026-08-08 entries above: the gate offers a waiver only for a release that *deliberately needs no golden*, and these need one. **The operator was asked and ruled bypass-now-bake-later on 2026-08-30**, on the stated ground that neither fix bites a DAY-0 box — R-330 is a nightly false alarm about apps a new box has not installed yet, and R-331 is a hub-side display over backups a new box has not taken yet — and both arrive by self-update afterwards. **That ground is recorded because it is the thing to re-check, not a general licence: the next release that changes first-boot behaviour cannot reuse it.** **OWED: bake a golden carrying 0.225.0 and vouch it** (`RUNBOOK-manual-build.md` §4.1; three-field change — `golden_version` + `agent_version` + `min_agent`; MinAgent is 0.129.0 per both CHANGELOG headers). **Cadence note, unchanged and now worse: this is the fourth bypass of this gate, and the gap it names is now two releases wide rather than one.** **⚠ WIDENED TO THREE THE SAME DAY — v0.226.0 (R-353/R-357/R-358/R-360) shipped 2026-08-30 and the golden still carries 0.223.0.** The `felhom.eu` push carrying that release's documentation used `git push --no-verify` on the operator's standing ruling from earlier the same day, declared in the commit and in `REPORT.md`. **The day-0 ground still holds for all three and was re-checked rather than assumed:** R-330 alarms about apps a new box has not installed; R-331 is a hub display over backups a new box has not taken; **R-353/357/358/360 are restore-surface fixes, and a day-0 box has nothing to restore.** **The ground expires the moment a release changes first-boot behaviour — that is the thing to re-check, not a licence.** **Owed: ONE bake carrying 0.226.0 covers all three** (`RUNBOOK-manual-build.md` §4.1; three-field vouch, MinAgent 0.129.0), then raise the floor. **✅ PAID THE SAME DAY — golden `0.226.1` baked, published, round-trip verified, VOUCHED, and the floor RAISED (2026-08-30).** Evidence: `documentation/tests/golden-0.226.1-2026-08-30/`. `golden_currency_gate.py` went **red → green** on the same command, which is its proof that it measures something real. **The three declared bypasses above are now HISTORICAL rather than standing.** **The round trip is the evidence, not the build log:** the published bytes were downloaded back — **657 197 592 B, sha256 `70ed8e93…baefe69`**, both identical to what the bake reported — and `./etc/felhom-controller-image` read **out of the downloaded archive** says `felhom-controller:0.226.1`, i.e. the delivered artifact naming the controller it will start. **The three-field vouch was checked deliberately, not assumed:** `MinAgent 0.129.0` read from the golden's controller CHANGELOG header, `agent_version 0.130.0 ≥ min_agent 0.129.0` (so NOT the R-216 shape), and the result verified by **re-reading the manifest** rather than trusting the flash — golden option `0.226.1 SELECTED`, all four shas matching. **The floor is proven ACTING, not merely set:** `demo-felhom` self-updated within 30 s, logging `[selfupdate] Post-update startup: update successful (0.225.0 → 0.226.1)`. **⚠ AND IT HAPPENED AGAIN THE SAME DAY, AND WAS PAID AGAIN.** v0.227.0/v0.227.1 (R-359/R-397) shipped after the 0.226.1 bake, the gate convicted a fifth time, that `felhom.eu` push used `--no-verify` and declared it, and golden **0.227.1** was baked, published, round-trip verified, **VOUCHED** and the floor **RAISED to 0.227.1** within the hour. Evidence: `documentation/tests/golden-0.227.1-2026-08-30/`. **THE CADENCE IS NOW MEASURED RATHER THAN ASSERTED: five convictions and two full bakes in one day.** Every bypass was declared and every debt was paid — but the pattern this row exists to name is exactly that a release and its delivery are separate acts, performed hours apart, by whoever remembers. **The floor was proven ACTING both times:** `demo-felhom` self-updated 0.225.0→0.226.1, then 0.226.1→0.227.1 — and the second time it also registered the new `offsite-integrity` job **by itself, on a box nobody deployed to**, which is the strongest evidence this row has ever carried that a floor delivers rather than merely records. **This row's OTHER half is still open and untouched: nothing gates the VOUCH itself** — the currency gate's own docstring says it checks the bake, so a baked-but-unvouched golden still passes it silently. **2026-08-31, the SEVENTH debt and it was paid the same day — twice in one day.** v0.230.0 shipped in the morning with the newest golden at 0.229.0, **which is the build R-403 says deletes a good copy**, so the gate was red across four commits (`dddcc80`, `6e550ae`, `130f7a6`, `32a4c35`). Golden **0.230.0** baked, published, round-trip verified, vouched, and the fleet floor raised 0.229.0 → 0.230.0; `demo-felhom` moved itself off the defective build unattended (`controller-swap: new controller healthy`, 16:21:40 CEST). Evidence: `documentation/tests/golden-0.230.0-2026-08-31/`. **The gate did its job and its own weakness surfaced doing it — R-410.** | **READY — the vouch half only** — owner Viktor **⚠ SIXTH CONVICTION, 2026-08-31 — controller v0.229.0 (R-102/R-103) is released and the newest golden carries 0.228.0.** `golden_currency_gate.py` FAILS, correctly: a machine installed right now receives 0.228.0 and neither of today's fixes. The `felhom.eu` push carrying this release's documentation used `git push --no-verify`, declared in the commit message and in `felhom-controller/REPORT.md` - **a BYPASS, not a waiver**, on the same reasoning as the five entries above: the gate offers a waiver only for a release that *deliberately needs no golden*, and this one needs one. **The day-0 ground was RE-CHECKED rather than reused:** R-102 and R-103 are restore-surface changes on the Tier-2 card, and a day-0 box has taken no Tier-2 copy and has nothing to restore from one; no first-boot behaviour changed, and `MinAgent` is unchanged at 0.129.0. **The ground still expires the moment a release changes first-boot behaviour.** **OWED: bake a golden carrying 0.229.0 and vouch it** (`RUNBOOK-manual-build.md` §4.1; three-field change - `golden_version` + `agent_version` + `min_agent`, MinAgent 0.129.0), then raise the floor. Fleet floor and golden are 0.228.0 today. **Golden and fleet delivery are the operator's (this row).** **✅ PAID THE SAME DAY — golden `0.229.0` baked, published, round-trip verified, VOUCHED, and the floor RAISED (2026-08-31).** Evidence: `documentation/tests/golden-0.229.0-2026-08-31/`. `golden_currency_gate.py` went **red to green** on the same command, which is its proof that it measures something real. **The `--no-verify` bypass declared above is now HISTORICAL rather than standing.** **The round trip is the evidence, not the build log:** the published bytes were downloaded back - **656 864 331 B, sha256 `39aa886d…d7bdae87`**, both identical to what the bake reported - and `./etc/felhom-controller-image` read **out of the downloaded archive** says `felhom-controller:0.229.0`. **A THIRD independent reader agreed before anything was vouched:** the hub's own Day-0 dropdown read the same sha straight from Gitea, a different code path from the round trip. **The three-field vouch was checked deliberately, not assumed** (`MinAgent 0.129.0` read from the golden's controller CHANGELOG header; `agent_version 0.130.0` >= `min_agent 0.129.0`, so NOT the R-216 shape; `agent_sha256` and `wrapper_sha256` carried through explicitly because the handler clears a field it is not sent), and verified by **re-reading the manifest** rather than trusting the flash. The **R-120 gate passed rather than being bypassed** - fleet newest 0.229.0, golden 0.229.0. **The floor is proven ACTING:** `demo-felhom` self-updated `0.228.0 -> 0.229.0` and logged `settle-gate: GO - at/above floor 0.229.0 (we are 0.229.0)` - **nobody deployed to that box.** **Cadence note: this is the SECOND bake in one day (0.228.0 then 0.229.0) and the sixth conviction, and both debts were paid within the hour.** This row's OTHER half is still open and untouched: **nothing gates the VOUCH itself** - the currency gate checks the bake, so a baked-but-unvouched golden still passes it silently. **⚠ SEVENTH CONVICTION, 2026-08-31 — controller v0.230.0 (R-403) is released and the newest golden carries 0.229.0. AND THIS ONE IS NOT LIKE THE OTHERS: the day-0 ground does NOT apply and must not be reused.** Every previous bypass rested on 'a day-0 box has nothing to restore / nothing to alarm about yet'. R-403 is a defect in the NIGHTLY TIER-2 COPY, which a day-0 box starts running on its first night: a machine installed on 0.229.0 can have a complete recovery package on its second drive replaced by an empty one, and that is measured, not suspected (120 082 104 B -> 7 036 B on demo-hp). **The row's own standing sentence - 'the ground expires the moment a release changes first-boot behaviour' - is what expires it here.** The `felhom.eu` push carrying this release's documentation used `git push --no-verify`, declared in the commit message and in `felhom-controller/REPORT.md` - a BYPASS, not a waiver. **OWED, and more urgent than the previous six: bake a golden carrying 0.230.0, vouch it (three fields, MinAgent 0.129.0 unchanged), and raise the floor.** `demo-hp` was updated by hand; `demo-felhom` is still on 0.229.0 and still carries the defect. **See also R-404, filed today: this is the seventh bypass and the habit is now the thing being reported.** **2026-09-01 (R-404): THE BAKE HALF IS UNCHANGED AND THE VOUCH HALF IS STILL OPEN.** R-404 moved WHO the bake check refuses and added a notice in the controller repo; it did NOT touch what is checked. **Nothing gates the VOUCH.** A baked-but-unvouched golden still passes both the gate and the new notice, and the reason is unchanged and forced: the vouched version lives only in the hub's `hub_settings` table, there is no copy in git, and a hub-reading gate could not be `--fast` so it would run in neither the hook nor CI. **Do not read R-404's closure as closing this.** **2026-09-13 — NARROWED: the WAIVER half is BUILT (R-468).** The docstring's *"honest fix is a recorded waiver in the register, never a habit of bypassing"* is now a mechanism: `golden_currency_gate.py` reads `documentation/tests/golden-waiver.yml` (dated, ≤ 14 days, row-bound), turns a BEHIND conviction into a loud advisory while valid, and is red again when it expires — the difference from this row's original rule, which recurred the next day, is that a dated waiver cannot be forgotten. It never covers an UNRECORDED golden (R-385). Operator ruling the same day: goldens weekly and before any install, not per release. **What stays open on THIS row is exactly one thing: nothing gates the VOUCH.** The waiver does not touch it, and the reason it is unbuilt is unchanged (the vouched version lives only in the hub). | — | — | operator | | **R-468** | Box system & updates | P3 | **[P3-LOW] THE GOLDEN WAIVER — goldens on a cadence, not per release (operator ruling 2026-09-13).** 25 goldens in 26 days in August, almost one per release, because `golden_currency_gate.py` trips on every release by design and the only honest ways past it were a bake or a declared `--no-verify` (thirteen by 2026-09-01, R-404/R-417). **The ruling: bake WEEKLY, and always before any drill or fresh install.** Every release still raises the FLOOR, so both demo boxes keep getting each release in ~20 s; only the golden — which protects a fresh install and nothing else — moves to a cadence. **The mechanism (built 2026-09-13):** `documentation/tests/golden-waiver. **⚠ CORRECTED THE SAME DAY (R-472): between bakes the floor does NOT carry a release — the hub holds any floor above the vouched golden (publish-train rule 1), so releases between bakes reach the demo boxes only by hand-deploy.**yml`, four lines (`issued`, `expires`, `reason`, `register_row: R-468`), read by the gate. While valid, a golden BEHIND the record makes the gate print a loud ADVISORY and exit 0; when it expires the gate is red again until someone bakes or renews. **The 14-day cap is enforced by the gate, not the runbook** — a longer, undated, unparseable, reason-less or row-less waiver is INCONCLUSIVE (exit 2), never 0 and never silently ignored. **It never covers a golden that is UNRECORDED (R-385)** — that is not a cadence choice. **A dated waiver cannot be forgotten; it just expires** — the difference from R-242's original rule, which recurred the day after it was written. Tests: `scripts/test_golden_currency_gate.py` cases 5–15 (E/F/G/H, a 15-day, absent, unparseable, bad-row and empty-reason waiver each 2; the R-421 decoy — a file saying only `expires` — 2). **This is a PRE-CUSTOMER arrangement: the first external install retires it** (delete the file in that commit). Cadence written into `RUNBOOK-manual-build.md` §4.2 and the `felhom.eu` end-of-session checklist. **Does NOT touch R-242's open half (nothing gates the VOUCH).** | **WATCHING — rank P3-LOW; owner: CC (renew ≤ 14 days or bake); retire at the first external install** | — | — | CC | -## Monitoring & notifications — 17 rows (P2 2, P3 9, P4 6) +## Monitoring & notifications — 13 rows (P2 2, P3 8, P4 3) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| -| **R-243** | Monitoring & notifications | P2 | **A box in the R-241 state silently stops backing up off-site, and NO ALARM OF ANY KIND FIRES.** Found by the R-241 spike (2026-08-07) as a by-product; **not part of the walk's finding and not previously filed.** The R-241 state is self-locking in a second, worse way than the recovery-journey dead end: `escrow_state` is stuck `pending` forever (the auto-confirm flips only on a hash match, and the hash cannot match a key the box minted itself), and `runOffboxBackup` returns at the escrow gate (`offbox.go:743`) before touching anything. **So off-site backups never run again — and the hub never notices.** All three signals that could catch it are excluded, each for its own individually-correct reason, verified in the hub this session: `offsite_stale` — `isStale` (`monitor/offsite.go:135`) returns false unless `EscrowState == "escrowed"`, and its own comment reads *"Pending/disabled = normal onboarding, never stale"*, so the box is classified as **still being set up, forever**; `offsite_delivery_stuck` — `monitor/offsite_delivery.go:91` skips the `applied` shape, and delivery genuinely IS applied (the credential was consumed and the target is in every report); `backup_failed` — never fires, because nothing fails: the run returns `nil` before it starts. **Three correct exclusions leaving one state unobserved.** This is the same class as the workspace `CLAUDE.md` "presence is not success" rule, one level up: **the absence of a failure is being read as the presence of a working tier.** **Partly subsumed by R-241's fix** — a box that recovers leaves this state — but **not for a box that does not**, and the alarm gap is what makes "does not" survivable indefinitely. **Not fixed; no code written.** **⚠ UPDATED 2026-08-07 (v0.206.0) — the STATE this row describes can no longer be entered, but the ALARM GAP is untouched and the row stays open.** R-241's mint guard means a box no longer mints a key over a sealed package, so it no longer arrives in the "escrow stuck pending against a self-minted key" state by itself. **What replaces it is a state that is VISIBLE rather than silent:** the box declares `offsite.state=awaiting_recovery_key` and the customer is offered the recovery screen. **But the hub still raises nothing for it**, and for the same three reasons: `isStale` needs `escrowed`, the delivery checker skips the `applied` shape, and `backup_failed` needs a run that never happens. **So a box whose customer never acts still stops backing up off-site with no operator signal** — the difference is that the customer can now see it and act, where before nobody could. **The remaining work is an operator-side signal for a box held in `awaiting_recovery_key` past some age**, and it is deliberately not bundled into R-241's fix. **⚠ MEASURED ON A REBUILD, 2026-08-07 (fifth walk) — the gap is real for the state this row describes, and NOT for the state a rebuild produces.** 88 seconds after the walk5 guest was destroyed and rebuilt, the hub emitted `offsite_delivery_stuck` (**warning**) and wrote an **operator-channel** `notification_log` row recording `offsite_credential_restaged` / status **REFUSED** with an accurate reason — *"the credential was applied and worked; the target was lost afterwards … a guest rebuild does, R-193"*. So on the **regressed-apply** shape the operator IS told, promptly and correctly, and this row's *"skips the applied shape"* does not apply. The gap stands for a box that reaches the held state **without** a prior working tier in its report history. **Recorded so the row is not read wider than it measures.** | **VERIFY — built on hub main 2026-10-08 (`b119301c`), ships with tomorrow's hub release.** New operator-only `offsite_escrow_pending` (warning): off-site ON, escrow not done, no successful off-site run for 7 days (CC picked 7 days, `09` §3 decision 179 — operator may reverse); weekly re-send, persisted, clears with an info line. `08` §6.3. **Expected to fire for Tester 2 on the first sweep after the deploy** (hub read 2026-10-08: off-site on, escrow `pending`, never a success). | — | Deploy hub tomorrow; confirm the Tester 2 mail arrives (positive control); then close | operator | +| **R-243** | Monitoring & notifications | P2 | **A box in the R-241 state silently stops backing up off-site, and NO ALARM OF ANY KIND FIRES.** Found by the R-241 spike (2026-08-07) as a by-product; **not part of the walk's finding and not previously filed.** The R-241 state is self-locking in a second, worse way than the recovery-journey dead end: `escrow_state` is stuck `pending` forever (the auto-confirm flips only on a hash match, and the hash cannot match a key the box minted itself), and `runOffboxBackup` returns at the escrow gate (`offbox.go:743`) before touching anything. **So off-site backups never run again — and the hub never notices.** All three signals that could catch it are excluded, each for its own individually-correct reason, verified in the hub this session: `offsite_stale` — `isStale` (`monitor/offsite.go:135`) returns false unless `EscrowState == "escrowed"`, and its own comment reads *"Pending/disabled = normal onboarding, never stale"*, so the box is classified as **still being set up, forever**; `offsite_delivery_stuck` — `monitor/offsite_delivery.go:91` skips the `applied` shape, and delivery genuinely IS applied (the credential was consumed and the target is in every report); `backup_failed` — never fires, because nothing fails: the run returns `nil` before it starts. **Three correct exclusions leaving one state unobserved.** This is the same class as the workspace `CLAUDE.md` "presence is not success" rule, one level up: **the absence of a failure is being read as the presence of a working tier.** **Partly subsumed by R-241's fix** — a box that recovers leaves this state — but **not for a box that does not**, and the alarm gap is what makes "does not" survivable indefinitely. **Not fixed; no code written.** **⚠ UPDATED 2026-08-07 (v0.206.0) — the STATE this row describes can no longer be entered, but the ALARM GAP is untouched and the row stays open.** R-241's mint guard means a box no longer mints a key over a sealed package, so it no longer arrives in the "escrow stuck pending against a self-minted key" state by itself. **What replaces it is a state that is VISIBLE rather than silent:** the box declares `offsite.state=awaiting_recovery_key` and the customer is offered the recovery screen. **But the hub still raises nothing for it**, and for the same three reasons: `isStale` needs `escrowed`, the delivery checker skips the `applied` shape, and `backup_failed` needs a run that never happens. **So a box whose customer never acts still stops backing up off-site with no operator signal** — the difference is that the customer can now see it and act, where before nobody could. **The remaining work is an operator-side signal for a box held in `awaiting_recovery_key` past some age**, and it is deliberately not bundled into R-241's fix. **⚠ MEASURED ON A REBUILD, 2026-08-07 (fifth walk) — the gap is real for the state this row describes, and NOT for the state a rebuild produces.** 88 seconds after the walk5 guest was destroyed and rebuilt, the hub emitted `offsite_delivery_stuck` (**warning**) and wrote an **operator-channel** `notification_log` row recording `offsite_credential_restaged` / status **REFUSED** with an accurate reason — *"the credential was applied and worked; the target was lost afterwards … a guest rebuild does, R-193"*. So on the **regressed-apply** shape the operator IS told, promptly and correctly, and this row's *"skips the applied shape"* does not apply. The gap stands for a box that reaches the held state **without** a prior working tier in its report history. **Recorded so the row is not read wider than it measures.** | **VERIFY** — **2026-10-10 read: no mail yet, and none was due.** The row expected a mail on the first sweep after hub 0.144.0; the code's clock starts at the customer's FIRST host report when no off-site run ever succeeded (`escrowPendingAnchor`), and Tester 2 first reported 2026-10-04 ~16:06Z (hub events `appliance_bound` 16:06), so the 7-day line falls on **2026-10-11 ~16:06Z** — the first sweep after that must mail. Checked: the operator mailbox holds Tester-2 `host_down`/`expected_*` mails and no `offsite_escrow_pending`; the hub page shows off-site enabled, escrow not done. A hub DB read was refused by the permission check. **VERIFY — built on hub main 2026-10-08 (`b119301c`), ships with tomorrow's hub release.** New operator-only `offsite_escrow_pending` (warning): off-site ON, escrow not done, no successful off-site run for 7 days (CC picked 7 days, `09` §3 decision 179 — operator may reverse); weekly re-send, persisted, clears with an info line. `08` §6.3. **Expected to fire for Tester 2 on the first sweep after the deploy** (hub read 2026-10-08: off-site on, escrow `pending`, never a success). | — | 2026-10-12: confirm the `offsite_escrow_pending` mail for Tester-2 arrived (Gmail `subject:offsite_escrow_pending`); then close | operator | | **R-528** | Monitoring & notifications | P2 | **[P2-MEDIUM] Docker does not report an OOM kill inside a Felhom LXC guest: `OOMKilled` stays false and no `oom` event fires, so the v0.243.0 OOM line is not proven live.** MEASURED 2026-09-15 on scratch 9202 (Docker 29.8.0): Paperless capped at 128M restarted 11 times with `OOMKilled=false` and zero `docker events --filter event=oom`; a memory hog inside the running container was killed (rc 137) with the same silence (`E2-oom-signal-measure-9202.txt`). BIGNIGHT VM 333 did read `oomkilled=true`, so the shape differs by case. **Fix shape:** the agent reads the guest container cgroups' `memory.events oom_kill` counters (host-side, reliable), or the controller alarms on a restart-count trend **RE-MEASURED 2026-09-16 on the DRILL box (fresh install, nested VM 334, Docker in an LXC guest, controller 0.243.0), so the finding is not a property of one machine:** the Paperless webserver was capped at 128 M with `docker update --memory`; it restarted 9-10 times, and all three signals stayed silent - `OOMKilled=false` on every inspect, `docker events --filter event=oom` EMPTY for the whole window, the container's cgroup not visible from inside the guest, and `dmesg` unreadable there. Identical to scratch 9202. So the v0.243.0 OOM line cannot fire on ANY Felhom box as shipped, on either host. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-m1-oom.txt`. | **READY — rank P2-MEDIUM; owner: CC** **2026-09-17 (chaos night): an OOM WAS detected on a fresh box, and named precisely.** On `tester-1-022354` (controller 0.245.0, guest 9201, 6 GB RAM) immich’s Postgres was killed by the memory limit during its reverse-geocoding import, and the controller pushed `app_oom` (warning, operator-only): „Alkalmazás memóriája elfogyott: immich (immich-postgres) — egy folyamatát a memóriakorlát leállította” — naming the app AND the exact container. The visible consequence was `write CONNECTION_CLOSED immich-postgres:5432` and twelve restarts of immich-server. So on THIS box the OOM scan works and was the fastest route to the diagnosis; recorded here rather than filed as a new row. Evidence: `audits/evidence-chaos-night-2026-09-17/round-2.txt`. **2026-10-05 (burn-down night): a one-page design proposal (no code) is in `audits/night-burndown-2026-10-05/design-R-528.md`** — for the operator. **2026-10-06: STOPPED BEFORE ANY CODE — the measurement contradicted the design** (decision 155 said A then C). On scratch 9202, Docker 29.8.2, the `OOMKilled` flag was TRUE in all four shapes tried: a child process killed while the container kept running (`oom_kill` 0 → 3, flag true) and three main-process kills (exit 137, flag true); an `oom` event each time. The false flags of 2026-09-15 were on Docker 29.8.0. Cost read for the record: one exec read of 21 containers on demo-hp = 1.6 s. `audits/design-build-2026-10-06/`C/. **2026-10-06 18:24: operator ruling, option A (decision 157):** nothing is built; the row stays open as a WATCH for a box whose Docker reports a false flag. **Added:** a Docker engine set may not be approved until it reports a memory kill correctly on the boxes that ran it (built in this row's next session). **2026-10-06 (night): the Docker-approval memory-kill check is BUILT** — hub v0.140.0 (LIVE: the approval waits for a passing `oom_check` on every ring-0 box; a failed, errored or missing one blocks) and the agent's wrapper (`felhom-agent` `acccb66`, unreleased — ships as v0.150.0 after the 2026-10-07 read-back). Proven by hand on demo-hp's guest: `OOMKilled=true`, exit 137, the `oom` event — seen only with an events window ending after the run (the wrapper waits 2 s). `audits/readback-2026-10-07/`F/. | — | Release agent v0.150.0 + bundle and deliver it; then the row is a WATCH only | CC | | **R-211** | Monitoring & notifications | P3 | **Prometheus has no config-reloader — a rules change reaches the pod and is never read** | **READY (S) — NEW 2026-08-05** | — | Found while verifying R-205 rather than by looking for it. The `mon-system/prometheus` Deployment runs **one** container (`prom/prometheus:v3.12.0`) with **no `configmap-reload`/`prometheus-config-reloader` sidecar**. After the ArgoCD sync the updated `node-housekeeping-alerts.yml` was present **inside the pod** (`grep -c "and on(instance)"` → 3 on the mounted symlink) while the Prometheus **rules API still served the old expression** — for **4+ minutes**, with no error anywhere. It only took effect after an explicit `POST /-/reload`. **The consequence is general, not specific to R-205: every rule edit in this repo since the stack was built has silently not applied until something happened to restart the pod** — so "committed and synced" has never meant "in force", and ArgoCD reporting `Synced/Healthy` is true and beside the point. `--web.enable-lifecycle` IS already set, so the fix is small: add a reloader sidecar watching the ConfigMap, or a `checksum/config` pod annotation so a rules change rolls the pod. **Same class as the four *built-but-never-wired* seams** — the control exists, nothing walks it | CC | | **R-333** | Monitoring & notifications | P3 | **Two disk-health questions the deploy raised and did NOT act on.** **(a) The 55/60 °C bands are SPINNING-DISK bands applied to NVMe.** They were adopted unchanged from the operator's Prometheus config so the two systems cannot disagree — a deliberate, stated decision — but **measured on demo-hp 2026-08-14 the healthy Toshiba KXG50PNV1T02 NVMe idles at 53 °C, two degrees below Figyelmeztetés and seven below Hiba**, and NVMe routinely exceeds 60 °C under load with no fault whatever. As it stands a healthy customer NVMe under sustained write can be reported as **Hiba** — the single worst outcome this feature can produce. **(b) The agent runs bare `smartctl -a -j` with no `-n standby`** (`felhom-agent/internal/storage/hostops.go:368`), so every poll WAKES a spun-down drive; going 6h → hourly multiplies that by six. demo-hp is all-flash so the cadence measurement could not reveal it, and it was recorded rather than acted on per the task's own instruction. Mitigating datum from the fixture: the failing drive logged only **3375 load cycles in 60505 hours** (~one per 18h), i.e. that duty cycle barely spins down at all | **READY (S each) — NEW 2026-08-14** | — | (a) split the temperature bands by device class, or drop them for NVMe and rely on `critical_warning`; (b) add `-n standby` to the agent's smartctl invocation (an agent change, so fold it into R-330's session) | Viktor decides (a); CC does (b) | @@ -242,8 +236,6 @@ stopping line that lies. | **R-285** | Monitoring & notifications | P4 | **A planned, supervised reinstall pages the operator as if the machine had died — there is no notion of expected downtime anywhere.** During the 2026-08-09 rehearsal the hub sent, all `status: sent` to the operator channel: `host_stale` 08:58 UTC, `node_stale` 09:00, **`host_down` 09:28 (error)**, **`node_down` 09:30 (error)**, `host_leaf_changed` 09:31, `host_recovered` 09:31, `node_recovered` 09:34, `offsite_delivery_stuck` 09:34 — eight operator mails for work that was deliberate, attended and announced. **This is the OPPOSITE gap from the one R-281 filed:** the alarms are not missing, they are indiscriminate. `host_stale` at 30 min and `host_down` at 60 min (`monitor/host_staleness.go:22-23`, `downAfter = 2 * threshold`) cannot distinguish a wiped-on-purpose box from a dead one, and `host_leaf_changed` firing on a reinstall is correct-but-expected. **Note the interaction with the mute used on 2026-08-09 evening:** blocking a customer silences everything, so today the only two settings are *page me for planned work* and *tell me nothing at all*. **What is owed is a middle:** a maintenance window, or an operator-set expected-downtime flag, that suppresses staleness and leaf-change while leaving genuine faults audible | **READY (M) — NEW 2026-08-09** | — | The evidence is the operator's mailbox plus `events`/`notification_log` for 2026-08-09 | CC | | **R-886** | Monitoring & notifications | P3 | **DooPlex's Alertmanager cannot write its own state since the Longhorn restart of 2026-10-05 13:20Z** — every 15 min `Running maintenance failed … open /alertmanager/nflog.…: permission denied` (and the same for `silences`), 8 times by 14:21Z; the pod was recreated 13:20:48Z by that restart. Mail still goes out (`alertmanager_notifications_total{integration="email"}` 5 → 6, `failed_total` 0, 14:23Z), but a silence set now and the record of what was already sent do not survive the next pod restart — so a restart can re-send every active alarm or drop a silence. Likely collateral of the restart (volume ownership on re-attach), not measured. **Checked from source 2026-10-05 (burn-down round 2):** homelab-manifests@87dfc29 mon-system/alertmanager.yaml:137-247: the Deployment has NO securityContext / fsGroup / runAsUser at all (grep), runs prom/alertmanager:v0.34.1 (:199, non-root `nobody` image) with --storage.path=/alertmanager on the Longhorn PVC alertmanager-data (:202, :212-213, :245-247). The comment :239-244 asserts silences now survive a restart -- an invariant with no test, which is exactly what this row says broke. Last structural change 58d1cd2 (2026-08-14, 'give alertmanager re | **OPEN** | — | Compare the volume's file owner with the pod's `securityContext` (`fsGroup`/`runAsUser`); fix in homelab-manifests; prove with a silence that survives a pod restart | operator | | **R-884** | Monitoring & notifications | P4 | **ArgoCD app `monitoring` shows `Deployment/prometheus` OutOfSync** (seen 2026-10-05 while syncing the R-173 alarm rules; only the rules ConfigMap was synced, so the Deployment drift is untouched and its cause unknown). A full sync would change the running Prometheus in an unknown way. **Checked from source 2026-10-05 (burn-down round 2):** Strong lead from source: homelab-manifests@87dfc29 commit 53c6e99 (Renovate, 2026-10-03) changed ONLY mon-system/monitoring.yaml `prom/prometheus:v3.14.0` -> `v3.15.0` (monitoring.yaml:419), and the `monitoring` Application has no `automated` syncPolicy in git (argocd-apps/homelab.yaml:602-605). So the drift is most likely an unsynced Renovate bump, i.e. a full sync = Prometheus 3.14 -> 3.15 upgrade (plus pod restart; R-211: no reloader). Not confirmed live. | **OPEN** | — | `argocd app diff monitoring` (or the CR's resource diff) to see what differs, then decide git or live | operator | -| **R-906** | Monitoring & notifications | P4 | **A page can show „+ 5 további figyelmeztetés" (+5 more warnings) with no warning above it.** SEEN 2026-10-08 on scratch 9202 (controller 0.303.0): `GetAlerts` caps the list at 5 and counts the rest into the overflow line BEFORE the layout drops the alerts that belong on other pages (`disk-not-separate` is `Inline` + `PageOnly` dashboard/monitoring, `web/alerts.go` ~L281); on the launcher, apps, backups and system pages all five visible ones were such alerts, so only the overflow line rendered. Not fixed here (controller out of scope). | **VERIFY — fixed on main 2026-10-08, ships with tomorrow's controller release** (controller `d5f2e47`). `AlertManager.GetBannerAlerts(page, lang)` filters with the layout's own rule BEFORE the cap; `baseData` and /monitoring use it. `TestR906_*` (the 9202 case: nine inline disk warnings + hub off, on /launcher, /stacks, /backups, /monitoring, hu + en — red against 05e12921). Live on 9202 with a test build: the overflow line is gone and the three real banner warnings show (`audits/dashboard-layout-2026-10-08/`) | — | Release tomorrow; read one page on a box with inline warnings; close | CC | -| **R-911** | Monitoring & notifications | P4 | **On an English page the „data storage not reachable" banner stays Hungarian: „Adattároló nem elérhető: ".** SEEN 2026-10-08 on 9202 (test build, `?lang=en`), once R-906 let the real banner warnings through. Cause: the health check appends `warnFmtStorageUnavailable` as plain text (`internal/monitor/healthcheck.go:322`) with no `MsgRef`, so the banner has no key to render in English (the R-516 item 10 pattern). | **OPEN — owner: CC** | — | Give the warning a `MsgRef` (new key `alert`/`health` hu + en, the wire text unchanged); English page test for the banner | CC | ## Hub & operator — 11 rows (P2 1, P3 4, P4 6) @@ -261,7 +253,7 @@ stopping line that lies. | **R-814** | Hub & operator | P4 | `PBS-storage-1` (u629193, box 611421) still `status=active`, 19.9 MB | **VERIFY** (2026-10-03 triage: a July watch row with no id; given R-814. WAITING-ON-OPERATOR — no record found that the box was deleted.) — WAITING-ON-OPERATOR | operator console | Delete the box | operator | | **R-844** | Hub & operator | P4 | **The household's OS-update line exists only on the hub's customer timeline.** 2026-10-04: the box itself has no event surface for agent results (the controller UI shows no timeline), so `os_update_applied` is a hub customer event (info: recorded, never mailed). Its stored text is the hub's English sentence; the hu/en bundle text (`mail.event.os_update_applied`) is used only if it is ever mailed. Fix direction: a controller-side line (the controller already polls the agent's local API) when the box gets a household timeline. `audits/os-guest-lane-2026-10-04/partG/hub-customer-timeline-demo-hp.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A DESIGN** — a household timeline on the box does not exist yet. | — | — | CC | -## Business & legal — 11 rows (P2 4, P3 1, P4 6) +## Business & legal — 10 rows (P2 4, P3 1, P4 5) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -272,12 +264,11 @@ stopping line that lies. | **R-89** | Business & legal | P4 | Retention as a per-customer **commercial** policy on the hub | READY (increment 2) | — | Policy object + reconciler → ep0 prune job; keep box tokens write-only | CC | | **R-794** | Business & legal | P4 | **[P3-LOW] redis 7.4 (RSALv2 / SSPL, not OSI) runs as a private cache in seven apps: dawarich, docmost, immich, nextcloud, outline, paperless-ngx, romm.** READ 2026-10-02 (`audits/licences-2026-10-02/TABLE.md`). Read as permitted (a private cache only its app uses is not Redis offered as a service — inferred). Valkey (BSD-3) or redis 8 (AGPL option) removes the question. **Needs:** a ladder step per app to valkey or redis 8, through the harness — no hurry. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: the row itself says no hurry; usage read as permitted.** | — | — | CC | | **R-914** | Business & legal | P4 | **Write the Felhom Facebook Page skill from the spike's findings.** Spike 2026-10-08 (`audits/SPIKE-facebook-page-api-2026-10-08.md`): key valid, never expires; the Page (`1360018983863273`) is reached with CREATE_CONTENT/MODERATE/ANALYZE; a scheduled text post and a scheduled photo were created, read back byte-equal and deleted (removal proven). Probe `scripts/facebook/fb_probe.py`. Gap to close in the skill: a scheduled photo's publish state was not read (no `post_id` returned). **2026-10-08 (Page pictures task) — two more for the skill:** (1) **the removal proof was not a proof**: the text post's after-DELETE answer was (#10) „does not exist, cannot be loaded due to missing permission…" — that message also means a permission gap. The skill proves removal by listing the Page's scheduled posts before and after the delete (present, then absent); the operator's Planner view on 2026-10-08 showed nothing on 15 October (a different channel, by eye). (2) **pictures by API** (READ, developers.facebook.com/docs/graph-api/reference/page/picture and …/reference/page/): `POST /{page}/picture` needs the `MANAGE` task, which the robot does not have (measured task list); the cover is `POST /{page}` `cover=`, „only by the Page Admin or Page Editor with `EDIT_PROFILE`" + `business_management`. Neither names `pages_manage_metadata`. Today the pictures are uploaded by hand (`marketing/facebook/README.md`). **-- 2026-10-09: the first slice exists.** `scripts/facebook/fb_probe.py` gained a **`schedule-post`** sub-command (text + link, scheduled only) reusing the probe's token loader, redaction, Bearer call and evidence writer. Guards, each with a test (30 tests; all 30 pass on DooPlex, 2 skip on Windows for want of a tz database): **it cannot publish immediately** — `published` is always `false` and the test enumerates every keyword `schedule_form()` takes to prove none flips it (RED-PROOFED: the guard was removed and the test seen failing, `'true' != 'false'`); the body is **read from `COPY.md` by section**, never through a shell; `check_when()` refuses a time outside Meta's 10-minute..6-month window before the call; `budapest_to_epoch()` uses the real tz database and **refuses** rather than guessing a DST offset; `list_scheduled()` records which route answered and returns `None` when both are refused, so a caller cannot claim a removal it never saw. **What the full skill still needs:** the photo path (the spike could not prove a scheduled PHOTO stays hidden — deliberately not reused), the pin, comment moderation, and the insight read-back after a post. | **READY — owner: CC** | — | Write the skill (drafts scheduled for operator review by default); read a scheduled photo back through the Page's scheduled-post listing | CC | -| **R-916** | Business & legal | P4 | **The logo has no usable vector master: `website/assets/logo.svg` sets „felhom.eu" as live text in the fonts „M+ 2c" and „Vremena Grotesk", which DooPlex does not have, so every renderer here draws other letters.** SEEN 2026-10-08 (Facebook pictures task): librsvg drew the lettering in DejaVu; `fc-match` resolves the family to DejaVu Sans. The PNG (645 x 408) is the only faithful copy, which caps every picture made from it at about that size (`marketing/facebook/README.md`). **-- MEASURED 2026-10-09, and the premise is WRONG for the file as it stands today:** the lettering in `website/assets/logo.svg` is **already converted to outlines**. All five wordmark elements are `` with real `d=` geometry (`path1 path3 path5 path7 path8`), and **no `` element has any content** — the two that exist are empty leftovers. The `font-family:'M+ 2c'` / `'Vremena Grotesk'` strings that this row cites are Inkscape METADATA left behind on converted paths (`-inkscape-font-specification`), not live text; a grep for `font-family` finds them and reads as live text, which is the likeliest way the original diagnosis went wrong. **Proof from a renderer that does NOT have those fonts:** Chrome draws `logo.svg`'s „felhom.eu” identical to `logo.png`'s, side by side (2026-10-09). **NOT re-tested:** librsvg specifically — DooPlex has no `rsvg-convert`, `inkscape` or `cairosvg` installed today, so the original librsvg/DejaVu observation could not be reproduced either way. It does not change the structural fact: there is no font to substitute. **Consequence:** the 645 x 408 PNG is NOT the only faithful copy and does not cap picture size; `logo.svg` can be rendered at any size, and `website/assets/logo-mark.svg` was cut from it this day (defs + path6 + g2) to fix the website header. | **READY — re-check before doing any work: this may need nothing from the operator at all.** The row asked for an Inkscape pass on „the machine with the fonts”; the measurement above says the file is already outlined and the operator may have nothing to do | nothing — the dependency on „the machine with the fonts” appears to be void (see the measurement) | CC or operator: confirm the outlines render correctly at a large size in one renderer that is not a browser, then CLOSE this row — or, if something really is still live text, say which element. If nothing is done: an operator task sits on the list that probably does not exist | operator | | **R-917** | Business & legal | P4 | **`COPY.md` §2, the Page's longer description (867 characters), has nowhere to go: Facebook's current Pages experience has no long-description field at all.** FOUND 2026-10-08 (Page setup task, `audits/facebook-page-setup-2026-10-08/`). Looked in four places, all on the live Page as its admin: the Page's „Névjegy" tab (Rövid áttekintés / Személyes adatok / Részletek — only a 255-character „Bemutatkozás" and the pinned category), Business Suite's „Oldal módosítása" dialog (profile picture, cover, Bemutatkozás, category, phone, e-mail, address, website, social links — and nothing else), Facebook settings → „Oldal adatai" (redirects to the same Névjegy tab) and settings → „Oldal beállítása" (name, access, type, history, status, recommendation, messaging, data sharing). MEASURED by Graph with the Page token: `description`, `general_info` and `bio` all read `null`. Writing `description` by API would need `pages_manage_metadata`; the robot key's scopes are `read_insights, pages_show_list, business_management, pages_read_engagement, pages_read_user_content, pages_manage_posts, pages_manage_engagement, public_profile`, and the task's fences forbid adding a permission. So §2 is written, reviewed and unplaceable. **Options for the operator:** (a) leave §2 unused and let the 99-character intro plus the website carry it; (b) shorten §2 to ≤ 255 characters and make it the „Bemutatkozás" instead of §1 — but §1 was written for exactly that slot, so this is really „rewrite one of the two"; (c) publish §2 as the Page's first pinned post once the app is Live (R-915), which is where a long text actually gets read; (d) ask Meta support whether the field still exists for this Page type. Recommended (c) — the text reads like a post already. **-- SCHEDULED 2026-10-09, option (c) carried out.** The text was shortened from §2 into `COPY.md` §6 (two versions; the operator chose **6.2 Közepes**, 638 Unicode characters, Hungarian only, 0 emoji, 0 hashtags). Scheduled through the robot, **not published**: post `1360018983863273_122096547315511222`, due **2026-10-12 19:00 Europe/Budapest** (epoch 1791824400 = 17:00 UTC, checked against the tz database). Read back: `is_published` **False**, scheduled time sent == read, message **hex-equal** to `COPY.md` §6.2 — and the sha256 recomputed OUTSIDE the probe agrees (`887514383eff997c`). Present in `GET /{page}/scheduled_posts`, and visible in Planner on H 12 at 19:00 with the link card attached (a different channel from the API). It can still be changed or deleted there. The number in the post was checked, not copied: the apps page carries 57 cards while saying 56, which is DELIBERATE — „6 alkalmazás + 1 beépített”, the 57th being FileBrowser, built into every box; `index.html` says „56 telepíthető alkalmazás” too. | **VERIFY -- scheduled on `main` 2026-10-09, due 2026-10-12 19:00. Not public yet; the operator reviews it in Planner and confirms after it goes out** | the scheduled time passing, then the operator's look | Operator: after it publishes on Monday evening, pin it — „…” → „Kiemelés”. Close on that word. CC (next session): read the post back — `is_published` must be **true** and the permalink must resolve; **if it did not publish, report it and do NOT post again**. If nothing is done: the post goes out anyway at 19:00 and simply is not pinned | operator | | **R-919** | Business & legal | P3 | **On a phone the Facebook Page cuts the left edge of the cover: the „s” of „saját szabályaid” and the „f” of „felhom.eu” are gone.** MEASURED 2026-10-08 on the live Page in Chrome DevTools device mode, Pixel 9 (412 × 924, mobile user agent, after a reload so Facebook serves the mobile bundle): `audits/facebook-page-setup-2026-10-08/C2-phone-headline-cut-closeup.png`. The mobile Page header is **412 × 274 = 1,504:1**, so Facebook keeps our cover's full height and shows only the centre **938 px of its 1640 px width (57,2 %)** — **351 px cut from each side**. `marketing/facebook/build.py` builds to a safe area of the centre **1028 × 544** (306 px clear of each edge), which is **45 px wider per side than the phone actually shows**; the light text in `out/cover-c.png` runs from x 333 to x 997, and the left crop edge is x 351, so the first **18 px** of the text are cut. Covers A and B are built from the same safe area and will have the same edge. Two further facts this measurement establishes: **Meta's own help page is wrong about its own rendering** — it states the mobile cover is 2,4:1 where the Page header measures 1,504:1 — and the mobile profile circle is far bigger than assumed (172 px, centred, overlapping the bottom 112 px of the 274 px cover, i.e. source x 637–1030 × y 369–624 is hidden). Not re-cropped on Facebook, per the task's fence. **-- 2026-10-09, FIXED in the build:** `marketing/facebook/build.py` now takes the phone view from the measurement (`PHONE_HDR = (412, 274)` -> the centre 938 px), so SAFE is (391, 40)-(1249, 584) and the phone profile circle is the measured CENTRED box (637, 369, 393) rather than a left-anchored one. Every cover is drawn in a derived `BAND` (408, 48)-(1249, 340), and a `cap` check holds each headline's capital at >= 4 % of the cover height. The red-proof runs on EVERY build (`control_old_window`): it draws the headline where the old 640 x 360 assumption put it, x 328, and the check must reject it -- the phone's crop edge is x 351, so 23 px were cut. Against the three covers as committed at `a76207945e` the new check convicted 3 of 3 (A 23/22 px over the left/right edges, B 63/58 px plus 1017 content pixels under the phone circle, C 63/82 px plus 1778). The profile pictures are untouched -- sha256 identical before and after. **-- 2026-10-09 (operator refinements, same day):** the profile picture now carries the logo MARK only (the lettering was unreadable at 176 px; the mark grew 69 % -> 76 % of the circle, canvas 932 -> 648), and the covers set the headline the way `site.css` sets `.page-index .hero-text h1` (Bold 700, letter-spacing -0.03em, not ExtraBold 800 untracked) with „felhom.eu” drawn from the logo's OWN lettering instead of typed. No geometry changed; every R-919 check and the red-proof stand. **-- 2026-10-09 (second measurement):** a phone has TWO views and they disagree. SIGNED IN the sides are cut (confirmed on the operator's REAL phone, Chrome/Android: the window solves to x 351..1298 against the emulator's 351..1289 - the left edge to the pixel). SIGNED OUT the FULL width is shown but the cover is top-anchored and only the top 525 px survives (the bottom 99 px is cut; the file's blue top rule is still visible, which is how the side was established), with a much bigger, higher circle at x 486..1150 from y 232. `build.py` now carries both views, models the circles as DISCS rather than rectangles running to the bottom, and splits the artwork into a READ layer (must survive every view) and a DECOR layer (may be cropped or covered; the build reports the cost - B 39 %, C 62 %). The two-view geometry convicted all three then-current covers before the redraw (A 908 px under a circle, B 1715, C 1962). Covers redrawn: C is the operator's laptop idea with the dashboard at 640 px (was 370), B's motif grown to match. **-- 2026-10-09 (the APP measured):** the operator checked the live Page in Facebook Lite and in Chrome on his phone. Solved against the laptop frame, both give a visible window of x 349..1290 / 349..1291 - the LITE APP CROPS EXACTLY LIKE SIGNED-IN MOBILE WEB, and both agree with the emulator's 351..1289. Their circle is at x 645..1021 from y 451, LOWER than the emulator's 369, so that figure was pessimistic rather than wrong. Five views measured; the signed-out one stays the binding constraint. Cover C redrawn to the operator's layout (wordmark 88 px on top, gap, catchphrase) - one line, not his two, because two measured 392 text pixels behind the signed-out circle („saját szabályaid” read „saját szab”) and sizing them to fit drops the capital to the 25 px floor. | **VERIFY -- rebuilt on `main` 2026-10-09. The app IS now measured and the cover renders correctly there; what is left is the operator's look at the NEW cover in the app after uploading it.** | — | Operator: upload the rebuilt cover-c (and the profile picture if not already), then confirm in the Facebook app that the wordmark and the catchphrase are whole. Close on that word | CC | | **R-920** | Business & legal | P4 | **The Messenger „Gyakori kérdések" automation does not exist for this Page, so `COPY.md` §5 — four questions with answers condensed from `gyik.html` — has nowhere to go.** FOUND 2026-10-09 (Page details task, `audits/facebook-page-details-2026-10-09/`). SEARCHED, not assumed absent: the create-automation catalogue („Az összes automatizálás") holds exactly THREE templates — Automatikus válasz, Távolléti üzenet, A megválaszolatlan üzenetek azonosítása (`B5-no-faq-template-all-three.jpg`); the template search for „kérdés" answers **„Nincs a keresésnek megfelelő automatizálási sablon."** while the POSITIVE CONTROL „üzenet" returns two, so the search works and the term genuinely misses; the existing instant-reply automation carries only channel, message and media — no FAQ and no quick replies; and the business-portfolio settings have no messaging/FAQ entry. The copy is written, sourced line by line to `gyik.html` and committed as `marketing/facebook/COPY.md` §5, ready to paste unchanged the day the feature appears. **Same shape as R-917** (§2 has nowhere to go), and the same cause: Meta removed a Page field this project had planned copy for. **Options for the operator:** (a) leave §5 unused until Meta brings the feature back; (b) fold the four answers into the Messenger welcome message (§3) — it holds 500 characters and today uses 120, so one or two would fit, not four; (c) publish them as a pinned FAQ post once the app is Live (R-915); (d) ask Meta support whether the FAQ automation still exists for this Page type. Recommended (a) with (c) later — the welcome message stays short, and the website's own `gyik.html` already answers these. | **DEFERRED — operator chose (c) on 2026-10-09:** park the text and publish it as a post once posting starts. Not a defect, and not waiting on CC | nothing — **UNBLOCKED 2026-10-09**: R-915 is closed, the app is Live, so posting can start whenever the operator wants | When posting starts: publish `COPY.md` §5 (the four Messenger FAQ answers) as one later post. If nothing is done: the text stays in `COPY.md` unused, which the operator has accepted | operator | -## Process & tooling — 23 rows (P3 3, P4 20) +## Process & tooling — 19 rows (P3 3, P4 16) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -300,10 +291,6 @@ stopping line that lies. | **R-759** | Process & tooling | P4 | **[P3-LOW] wger's onboarding record (the checklist pilot) keeps rows open that no other row owns.** 2026-10-01, `app-catalog-felhom.eu/onboarding/wger.md`: **2.5** no backup → remove → restore → read back of wger exists — the box has no per-app backup press outside an Update (R-648) and wger has no newer step to carry one; **3.7** changing the password and adding a family member not measured, and the template has no `add_people` text; **6.3** no forced-fail undo for wger; **8.2** the app page not read on 9202 this session; **9.1** the runtime volume-persistence gate not re-run (last CLEAN 2026-08-02). The other open rows have their own: 1.5 (R-755), 1.7/2.8 (R-762), 3.4 (R-763), 7.1 (R-764). wger is exempt from the onboarding gate (published before the checklist), so nothing blocks; this row is what keeps the record honest. **Needs:** the five measured on 9202 — 2.5 and 6.3 ride wger's next ladder step (the update's backing-up phase is the per-app backup). `audits/new-app-checklist-2026-10-01/` | **READY — rank P3-LOW; owner: CC (catalog)** **Re-ranked 2026-10-03: P3→P4: record-keeping for a hidden app; nothing blocks.** | — | — | CC | | **R-786** | Process & tooling | P4 | **[P3-LOW] SparkyFitness's onboarding record has six open rows** (`app-catalog-felhom.eu/onboarding/sparkyfitness.md`): 0.5 runtime internet (food search providers), 0.7 the phone app's sign-in route through traefik, 1.6 the env names the server reads, 1.7 the entrypoint read, 5.4 a second memory watch at another limit, 8.3 no logo/screenshots on felhom.eu (404). Everything else measured this session (bench + 9202). **Needs:** each row measured, or n/a with a reason. | **READY — rank P3-LOW; owner: CC (catalog)** **Re-ranked 2026-10-03: P3→P4: onboarding record completeness; no household meets it directly.** | — | — | CC | | **R-807** | Process & tooling | P4 | **[P3-LOW] 13 apps stay UNDETERMINED because their seeds write only to the database, so their upload / media / cache / redis volumes stay empty.** MEASURED 2026-10-02 (`audits/persistence-sweep-2026-10-02/A/sweep/TABLE.md`): claper, crafty-controller, dawarich (redis), docmost, gramps-web, immich (ML cache), outline (+redis), sparkyfitness, tandoor, vikunja, wger, wishlist, zipline — plus plex (no seed route: a plex.tv claim token) and wanderer (no fixture; unhealthy under the gate). None is BROKEN: nothing was written outside a preserved folder. **Needs:** per app, a seed that uploads one file (or the volume named n/a with a reason: a redis cache, an ML model cache). | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: test coverage; nothing was found broken.** | — | — | CC | -| **R-896** | Process & tooling | P4 | **The catalog's `volume-persistence` gate runs its canary and the apps on the LOCAL Docker, so a full gate run on DooPlex acts on the production host — and there its self-test fails for every app.** SEEN 2026-10-06 night: a helper's full `catalog_gates.py` run on DooPlex built `felhom-volgate-canary:1` and ran the canary pair locally (`scripts/check-volume-persistence.py` canary self-test, ~line 830); it reported the self-test failing for every app, also on `main` (checked with gokapi). Not re-run (it would act on DooPlex again). Same class as R-650 (tests that reach real Docker act on DooPlex). | **OPEN — filed 2026-10-06 night** | — | Point the gate at the bench guest (as the upgrade harness does) or refuse to run on a host without a marker; then find why the self-test fails | CC | -| **R-903** | Process & tooling | P4 | **On a phone with JavaScript off, the website's menu does not open — so its links and the language globe cannot be reached there.** MEASURED 2026-10-08 (globe brief, headless Chrome at 376 px, JavaScript off): the three-line button is scripted (`#hamburger` toggles `.nav-links.is-open`), and below 900 px the nav list sits off-canvas until then. Older than the globe — the text switch had the same limit. The globe itself works with JavaScript off at desktop width (`audits/website-globe-2026-10-08/check.txt`). Fix direction: a CSS-only menu toggle (a `
` or a checkbox), a nav change on every page plus gate 3. | **OPEN — owner: CC** | — | Build the no-JS menu in a website task | CC | -| **R-910** | Process & tooling | P4 | **The website's dashboard pictures show the old Apps card (the tags crowd the name, R-909) and no phone view (R-907).** Filed 2026-10-08. | **OPEN — owner: CC** — waits for tomorrow's controller release to reach demo-hp | Tomorrow's controller release on demo-hp | After the release reaches demo-hp 9201: retake the 8 `dashboard-*.webp` pictures the way `audits/website-dashboard-2026-10-08/` did (read only, language round trip), consider adding the phone view, run `site_gates.py`; then close R-907, R-909 | CC | -| **R-912** | Process & tooling | P4 | **No gate checks that every catalog app has a logo file under its slug.** SEEN 2026-10-08: five apps (Crafty Controller, Gramps Web, Home Assistant, Plant-it, Uptime Kuma) had their logos and pictures under other names, so the dashboard showed the grey placeholder and no pictures; Docmost and Recipe Importer were added to the website today and are not in the hub's copy yet. Renamed the same day (website `CHANGELOG` 2026-10-08 evening). | **OPEN — owner: CC** | — | A gate (catalog or website): for every `templates//.felhom.yml` slug, `website/assets/-logo.svg` or `.png` exists, and no logo is coloured; with a decoy | CC | diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index fa405d82..cad651ae 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2026-10-10 (afternoon) — site gate 21: every catalog app has its logo under its slug (R-912) + +- `site_gates.py` gate 21 reads `app-catalog-felhom.eu/templates/*/.felhom.yml` (the `slug:`, else the directory) and + requires `website/assets/-logo.svg` or `.png` — the name the dashboard asks for. 60 of 60 today. No catalog + clone → „NOT CHECKED" said out loud; a clone with no templates → a failure (the gate checked nothing). +- Decoy `site/app-logo-under-another-name` in `test_gate_decoys.py`: Crafty Controller's logo renamed to + `crafty-logo.png` (the measured 2026-10-08 shape) — convicts with gate 21's own line. + ## 2026-10-09 (late afternoon) — dooplex-offsite carries the operator signing keys (R-924, operator ruling option A) - `felhom-dooplex-offsite`: `felhom-op-operational`, `felhom-rec-recovery`, `felhom_op_ed25519` and their `.pub` (from diff --git a/scripts/site_gates.py b/scripts/site_gates.py index 0b6875ce..06e2d8af 100644 --- a/scripts/site_gates.py +++ b/scripts/site_gates.py @@ -31,8 +31,12 @@ Gates (all must pass; non-zero exit on any failure): 19. dash-lang — a page shows dashboard pictures only in its own language (`dashboard-*-hu.webp` / `-en.webp`) 20. viewer — data-gallery openers are to existing assets; gallery.js (with ?v=) exactly where openers are; no inline viewer copy + 21. app logos — every catalog app (`app-catalog-felhom.eu/templates/*/.felhom.yml`, by its `slug:`) has + website/assets/-logo.svg or .png — the dashboard asks for exactly that name and shows a grey + placeholder otherwise (R-912: five apps had theirs under other names). The sibling catalog clone + absent → said out loud, not checked """ -import html as _html, io, json, os, re, sys, unicodedata +import glob, html as _html, io, json, os, re, sys, unicodedata # A gate's own console must not decide its verdict. These scripts quote back Hungarian copy, file # paths and arrow characters; a Windows console here is cp1250, and printing one of them raised @@ -497,6 +501,25 @@ for p, s in pages.items(): fail("%s: an inline copy of the picture viewer — it lives only in assets/gallery.js" % p) print(" picture viewer: %d openers, every one a link to an existing file" % _open_n) +# gate 21: every catalog app has its logo under its slug (R-912) +_CAT = os.path.join(os.path.dirname(ROOT), "app-catalog-felhom.eu", "templates") +if not os.path.isdir(_CAT): + print(" app logos: NOT CHECKED — no catalog clone at %s" % _CAT) +else: + _slugs = [] + for _f in sorted(glob.glob(os.path.join(_CAT, "*", ".felhom.yml"))): + _m = re.search(r'^slug:\s*["\']?([A-Za-z0-9._-]+)', io.open(_f, encoding="utf-8-sig").read(), re.M) + _slugs.append(_m.group(1) if _m else os.path.basename(os.path.dirname(_f))) + if not _slugs: + fail("app logos: the catalog clone at %s holds no templates/*/.felhom.yml — the gate checked nothing" % _CAT) + _missing = [x for x in _slugs if not any(os.path.isfile(os.path.join(W, "assets", "%s-logo.%s" % (x, e))) + for e in ("svg", "png"))] + if _missing: + fail("app logos: no website/assets/-logo.svg or .png for %s — the dashboard shows a grey placeholder" + % ", ".join(_missing)) + else: + print(" app logos: %d catalog apps, each with its -logo file" % len(_slugs)) + if fails: print("\nSITE GATES FAILED: %d problem(s)" % len(fails)) sys.exit(1) diff --git a/scripts/test_gate_decoys.py b/scripts/test_gate_decoys.py index 0610e28a..996d277d 100644 --- a/scripts/test_gate_decoys.py +++ b/scripts/test_gate_decoys.py @@ -91,7 +91,7 @@ COVERS = { "switch at the wrong twin, lang=\"hu\" on an English page, an app missing from one apps page, " "ASCII-only Hungarian in English text and in a script message, a 'please', an English " "retrieval promise the Hungarian does not make, a FAQ JSON-LD drifting from the visible " - "answer, text after , a Hungarian dashboard picture on an English page, and four picture-viewer faults (an opener without href, an opener to a missing file, openers with no gallery.js, the inline viewer put back) — each must convict WITH its own failure line"), + "answer, text after , a Hungarian dashboard picture on an English page, and four picture-viewer faults (an opener without href, an opener to a missing file, openers with no gallery.js, the inline viewer put back), and (R-912) a catalog app's logo under another name than its slug — each must convict WITH its own failure line"), "script-tests": ("R-885: FIVE cases in scripts/test_script_tests_gate.py, run from here: a suite that PRINTS " "OK and exits 1 (label without fact), a failing suite three levels deep (scope is a walk), " "an empty scripts/ tree (checked nothing), the genuine article (must pass), the nested mark"), @@ -549,6 +549,18 @@ decoy("site/globe-wrong-order", "site_gates.py", decoy("site/leftover-text-switch", "site_gates.py", replace_in(_EN("apps.html"), '
  • ', '
  • Magyar
  • '), must='en/apps.html: a leftover text language switch') +# R-912 (2026-10-10, gate 19): the measured 2026-10-08 shape — Crafty Controller's logo under ANOTHER name +# (`crafty-logo.png`), so the dashboard asked for `crafty-controller-logo.*` and showed the grey placeholder. +def _rename(src, dst): + def _plant(): + os.rename(src, dst) + return lambda: os.rename(dst, src) + return _plant + + +decoy("site/app-logo-under-another-name", "site_gates.py", + _rename(os.path.join(_WEB, "assets", "crafty-controller-logo.png"), os.path.join(_WEB, "assets", "crafty-logo.png")), + must="app logos: no website/assets/-logo.svg or .png for crafty-controller") # the dashboard pictures (2026-10-08): a Hungarian picture copied onto the English home page decoy("site/dashboard-picture-wrong-language", "site_gates.py", replace_in(_EN("index.html"), 'src="/assets/dashboard-start-en.webp"', 'src="/assets/dashboard-start-hu.webp"'), diff --git a/website/404.html b/website/404.html index d1cf98f1..62c24841 100644 --- a/website/404.html +++ b/website/404.html @@ -6,7 +6,7 @@ Az oldal nem található | Felhom.eu - + diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index ba44528b..4363688b 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,3 +1,16 @@ +## 2026-10-10 (afternoon) — the phone menu works with JavaScript off; the dashboard pictures retaken + +- **R-903: on a phone with JavaScript off the menu links and the language globe were unreachable** (the three-line + button is scripted; the list sat off-canvas). `site.css` now has `@media (max-width: 900px) and (scripting: none)`: + the list sits in the header as a wrapped row and the header scrolls with the page. Script-on pages are unchanged. + `site.css?v=9` on all 18 pages. Measured in headless Chrome at 376 px, JavaScript off, `/` and `/en/`: links reachable + 0/7 before, 7/7 after; with JavaScript on, the closed menu is as before + (`documentation/audits/register-shrink-2026-10-10/r903-phone-nojs.txt`). +- **R-910: all eight `dashboard-*.webp` retaken** on demo-hp's household guest (Launcher, Apps, Paperless-ngx's page on + controller 0.305.0; Backup → Apps on 0.307.0), hu + en, 1440 × 900, WebP q82. The Apps card shows the new header + (tags on their own row). Captions unchanged — every caption's claim still holds. Privacy scan: + `documentation/audits/register-shrink-2026-10-10/r910/`. + ## 2026-10-10 — two new apps on the apps page: Grocy and LubeLogger - **Two cards added** to the „Otthon & Életmód" / "Home & Lifestyle" section of both apps pages, in the diff --git a/website/adatkezeles.html b/website/adatkezeles.html index c7d02e95..ad16565e 100644 --- a/website/adatkezeles.html +++ b/website/adatkezeles.html @@ -19,7 +19,7 @@ - + diff --git a/website/alkalmazasok.html b/website/alkalmazasok.html index 85c21823..1b9c2ffb 100644 --- a/website/alkalmazasok.html +++ b/website/alkalmazasok.html @@ -43,7 +43,7 @@ } - + diff --git a/website/assets/dashboard-app-en.webp b/website/assets/dashboard-app-en.webp index bfff90c5..ebda0218 100644 Binary files a/website/assets/dashboard-app-en.webp and b/website/assets/dashboard-app-en.webp differ diff --git a/website/assets/dashboard-app-hu.webp b/website/assets/dashboard-app-hu.webp index 7b034708..5a42d929 100644 Binary files a/website/assets/dashboard-app-hu.webp and b/website/assets/dashboard-app-hu.webp differ diff --git a/website/assets/dashboard-apps-en.webp b/website/assets/dashboard-apps-en.webp index f5312187..6e8cd364 100644 Binary files a/website/assets/dashboard-apps-en.webp and b/website/assets/dashboard-apps-en.webp differ diff --git a/website/assets/dashboard-apps-hu.webp b/website/assets/dashboard-apps-hu.webp index 49e9d55f..0ca047ba 100644 Binary files a/website/assets/dashboard-apps-hu.webp and b/website/assets/dashboard-apps-hu.webp differ diff --git a/website/assets/dashboard-backups-en.webp b/website/assets/dashboard-backups-en.webp index c5d045e8..92eeb48d 100644 Binary files a/website/assets/dashboard-backups-en.webp and b/website/assets/dashboard-backups-en.webp differ diff --git a/website/assets/dashboard-backups-hu.webp b/website/assets/dashboard-backups-hu.webp index 8b1781dc..1ce5e4c1 100644 Binary files a/website/assets/dashboard-backups-hu.webp and b/website/assets/dashboard-backups-hu.webp differ diff --git a/website/assets/dashboard-start-en.webp b/website/assets/dashboard-start-en.webp index ae126b17..1001ede7 100644 Binary files a/website/assets/dashboard-start-en.webp and b/website/assets/dashboard-start-en.webp differ diff --git a/website/assets/dashboard-start-hu.webp b/website/assets/dashboard-start-hu.webp index e630bf09..3e084afb 100644 Binary files a/website/assets/dashboard-start-hu.webp and b/website/assets/dashboard-start-hu.webp differ diff --git a/website/assets/site.css b/website/assets/site.css index 40097c3d..a58d98c1 100644 --- a/website/assets/site.css +++ b/website/assets/site.css @@ -309,6 +309,31 @@ footer a:hover, footer a:focus-visible { text-decoration: underline; } body.menu-open { overflow: hidden; } } +/* R-903 (2026-10-10): with JavaScript off the menu button cannot open the off-canvas list, so on a phone the + links and the language globe were unreachable. With no scripting the list sits in the header as a plain + wrapped row instead, and the header scrolls with the page so it can grow. Script-on pages are unchanged. */ +@media (max-width: 900px) and (scripting: none) { + nav { position: static; } + nav .container { flex-wrap: wrap; row-gap: 12px; } + .hamburger { display: none; } + .nav-links { + position: static; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + height: auto; + padding: 0; + gap: 4px 20px; + background: none; + border-left: none; + transition: none; + overflow: visible; + } + .nav-links li { border-bottom: none; } + .nav-links a { padding: 6px 0; font-size: 1rem; } + .lang-globe-btn { padding: 6px 0; } +} + @media (max-width: 600px) { .section-header h2 { font-size: 1.8rem; } .content-section { padding: 60px 0; } diff --git a/website/biztonsagimentes.html b/website/biztonsagimentes.html index 81e9a583..590e8d11 100644 --- a/website/biztonsagimentes.html +++ b/website/biztonsagimentes.html @@ -37,7 +37,7 @@ } - + diff --git a/website/en/apps.html b/website/en/apps.html index 58a0cb2b..51ce9556 100644 --- a/website/en/apps.html +++ b/website/en/apps.html @@ -43,7 +43,7 @@ } - + diff --git a/website/en/backups.html b/website/en/backups.html index c1a6d6c2..c5a57f11 100644 --- a/website/en/backups.html +++ b/website/en/backups.html @@ -37,7 +37,7 @@ } - + diff --git a/website/en/contact.html b/website/en/contact.html index cc3abf05..6a146172 100644 --- a/website/en/contact.html +++ b/website/en/contact.html @@ -26,7 +26,7 @@ - + diff --git a/website/en/download.html b/website/en/download.html index 9dd4d46d..e9008412 100644 --- a/website/en/download.html +++ b/website/en/download.html @@ -24,7 +24,7 @@ - + diff --git a/website/en/faq.html b/website/en/faq.html index 10b64b35..796c1edf 100644 --- a/website/en/faq.html +++ b/website/en/faq.html @@ -363,7 +363,7 @@ } - + diff --git a/website/en/index.html b/website/en/index.html index ffa0254c..37875117 100644 --- a/website/en/index.html +++ b/website/en/index.html @@ -54,7 +54,7 @@ } - + diff --git a/website/en/technology.html b/website/en/technology.html index 7a3e2654..25fe1c7c 100644 --- a/website/en/technology.html +++ b/website/en/technology.html @@ -38,7 +38,7 @@ } - + diff --git a/website/feltetelek.html b/website/feltetelek.html index e5cc4a8d..b7073ecc 100644 --- a/website/feltetelek.html +++ b/website/feltetelek.html @@ -19,7 +19,7 @@ - + diff --git a/website/gyik.html b/website/gyik.html index 7fec9e38..6534f741 100644 --- a/website/gyik.html +++ b/website/gyik.html @@ -363,7 +363,7 @@ } - + diff --git a/website/index.html b/website/index.html index 60bb052e..96b15134 100644 --- a/website/index.html +++ b/website/index.html @@ -54,7 +54,7 @@ } - + diff --git a/website/kapcsolat.html b/website/kapcsolat.html index 9e87ee4e..2ab42339 100644 --- a/website/kapcsolat.html +++ b/website/kapcsolat.html @@ -26,7 +26,7 @@ - + diff --git a/website/letoltes.html b/website/letoltes.html index 8ce7a732..98352702 100644 --- a/website/letoltes.html +++ b/website/letoltes.html @@ -24,7 +24,7 @@ - + diff --git a/website/szolgaltatasok-nonpublic.html b/website/szolgaltatasok-nonpublic.html index 7f12a725..e7fb233c 100644 --- a/website/szolgaltatasok-nonpublic.html +++ b/website/szolgaltatasok-nonpublic.html @@ -6,7 +6,7 @@ Szolgáltatások - Felhom.eu - + diff --git a/website/technologiak.html b/website/technologiak.html index 025ab1f3..0e9cc57a 100644 --- a/website/technologiak.html +++ b/website/technologiak.html @@ -38,7 +38,7 @@ } - +