docs: controller v0.255.0 — the globe fix on the sign-in-flow pages
gates / gates (push) Successful in 22s
gates / gates (push) Successful in 22s
R-579 filed and closed the same day: five shell templates loaded style.css with no cache-buster, so a browser holding the pre-0.254.0 file rendered the new globe unstyled; and the globe sat outside the card. - STATUS.md rewritten for the operator: what was seen, why, the third defect found while fixing it (version disclosure on the guest share page, caught by TestShareGuest_HeadersTilesNoAdminChrome), and the one decision left — raise the fleet floor to 0.255.0, with what happens either way. - 10-localisation.md §3: the shells' asset tag, and why the two guest pages get an opaque tag rather than the version. - Audit D: the parity diff (91 of 106 fixtures identical, every dashboard page among them) and the live endpoint evidence from demo-hp guest 9201. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,49 +1,36 @@
|
||||
# STATUS — what works, what's broken, what's next
|
||||
|
||||
**Updated 2026-09-18 (night) — the language work is finished, and the switch is now a globe.**
|
||||
**Updated 2026-09-18 (late) — the globe you photographed is fixed.**
|
||||
|
||||
> **Ready for a volunteer: yes.** A Hungarian household sees what it saw yesterday, apart from one
|
||||
> deliberate change: the two small "Magyar / English" links at the bottom of the menu are now a globe.
|
||||
> **Ready for a volunteer: yes.**
|
||||
|
||||
**Decisions I took.** One you were asked for, taken on its own stated default: **if someone switches
|
||||
the claim page to English and then claims the box, the box becomes English.** They chose it, and the
|
||||
first screen they see should be in it. You can reverse it; nothing else depends on it.
|
||||
**What you saw, and why.** The globe on the sign-in page came out as a bare triangle and two plain
|
||||
words, floating outside the card. Two separate faults:
|
||||
|
||||
**What I did today.** Three releases, and the language job is done.
|
||||
1. **Your browser was still using the old stylesheet.** Those pages asked for the style file without a
|
||||
version number on the end, so a browser that had already downloaded it kept the old copy — and the
|
||||
old copy knows nothing about a globe. The main dashboard has always asked with a version number;
|
||||
these pages never did. **Five pages had it, not three.**
|
||||
2. **Even styled, it sat outside the card**, pinned to the corner of the window, so it read as part of
|
||||
the browser rather than part of the page. It is now inside the card, centred under the footer, and
|
||||
the little menu opens upward.
|
||||
|
||||
1. The sentences the program writes into the pages.
|
||||
2. The error messages — about 180 of them, written deep inside the program.
|
||||
3. Today's last piece: **the notes the box saves overnight**, and **the globe**.
|
||||
**A third thing I found while fixing it.** My first fix would have printed the exact software version
|
||||
onto the page a guest opens from a share link — someone you sent a link to, who should not be told
|
||||
which build you are running. **An existing test caught it.** Those two pages now get a scrambled tag
|
||||
instead: it does the same job and says nothing.
|
||||
|
||||
**The globe.** Two text links at the bottom of the menu asked you to recognise two words as links,
|
||||
and they wrapped. Now there is one globe — the symbol everyone already reads as "language". Click it
|
||||
and a small list opens: Magyar, English, with the current one ticked. **The sign-in page and the claim
|
||||
page have it too**, which matters: someone who cannot read Hungarian could not previously find their
|
||||
way out of Hungarian before signing in. Their choice is kept in their own browser only — it never
|
||||
changes what the household has chosen, and a signed-in household never picks up a stranger's choice.
|
||||
**Checked on the demo HP box:** the page now asks for the versioned file, the file it gets back
|
||||
carries the new rule, and the old rule is gone. **What I cannot check is how it looks** — there is no
|
||||
browser on this machine. That part is your click.
|
||||
|
||||
**One thing to expect, and it is the choice you were offered.** The notes saved overnight are written
|
||||
in the box's language at the moment they are written. If you switch language, last night's note stays
|
||||
in the old language until the next night rewrites it.
|
||||
**Rows.** One opened and closed the same day.
|
||||
|
||||
**What broke, and what I did about it.** **I introduced a freeze.** The code that writes the overnight
|
||||
note asked the box "what language are you?" at a moment when that question could not be answered —
|
||||
and it would have hung there **forever, holding a lock the rest of the box needs**. On a real machine
|
||||
an overnight cloud backup would have stopped and taken everything else with it. The test run caught it
|
||||
by taking 25 minutes instead of 8. It is fixed, and there is now a check that names the exact line in
|
||||
a second instead of hanging. I also found and fixed a second one before it shipped: the recovery
|
||||
screen's globe would have looked like it worked and done nothing.
|
||||
|
||||
**Rows.** One closed (the whole language job), two opened. The register went from **269** to **271**.
|
||||
|
||||
**The floor is raised, as you asked.** The fleet minimum went from 0.253.0 to **0.254.0**. The N100
|
||||
demo box took it by itself in about forty seconds and is healthy, with its other four apps still
|
||||
running; its sign-in page now shows the globe and none of the old text links. The HP demo box already
|
||||
had it. **The two boxes that are switched off did not get it** — Peti's has been off for 65 days on a
|
||||
much older version, Tester 1 for a day — and they will take it on their own when they come back,
|
||||
which is how a floor always works. Neither has been tried on this version.
|
||||
|
||||
**Needs you.** Nothing. If you do nothing: the two sleeping boxes update themselves when they wake.
|
||||
**Needs you — one decision.** **Raise the floor to 0.255.0?**
|
||||
- **If you do:** every box serves the fixed pages on its next check-in, and nobody sees the broken globe.
|
||||
- **If you do nothing:** boxes on 0.254.0 keep showing it to anyone whose browser cached the old style
|
||||
file. Nothing is at risk; it just looks wrong.
|
||||
- I would raise it — the thing it fixes is the thing you noticed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -436,6 +436,16 @@ cannot inherit a language a previous visitor picked in the same browser. The rec
|
||||
measured live before it was right: an anonymous form there sets a cookie that `langFor` then ignores,
|
||||
and the button appears to do nothing.
|
||||
|
||||
**[FACT] Where the globe SITS, and a stale-stylesheet trap that only a screenshot could show
|
||||
(v0.255.0, R-579).** On the pages outside the dashboard chrome the globe is **inside the card, centred
|
||||
under the footer**, with the menu opening upward — the shared `.lang-globe-menu` rule, so the two
|
||||
surfaces cannot drift apart. It was first placed at the corner of the VIEWPORT, which read as a stray
|
||||
browser control rather than part of the page. And five of those shells requested `style.css` with **no
|
||||
`?v=`**, so a browser holding a copy from before the globe existed kept serving CSS with no
|
||||
`.lang-globe` rules and it rendered as a bare, unstyled `<details>`. **Every test passed, because they
|
||||
all read the markup and the fault was in which CSS file the browser fetched.** `Version` is now set in
|
||||
`executeTemplateLang`, once, for every shell.
|
||||
|
||||
**[DESIGN] `POST /lang` is CSRF-exempt, for a reason narrow enough to check.** The only achievable
|
||||
effect of a forged request is to change the language of the page the victim's own browser shows them.
|
||||
It writes one display-only cookie, reads nothing, touches no setting, and `safeBackPath` refuses a
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Live validation — controller v0.255.0 on demo-hp guest 9201 (2026-09-18)
|
||||
|
||||
**Method: endpoint-level.** No browser on DooPlex. Three GETs, no session needed, nothing written.
|
||||
The deploy endpoint was not touched (`.claude/rules/live-probes.md`).
|
||||
|
||||
```
|
||||
stylesheet link : <link rel="stylesheet" href="/static/style.css?v=0.255.0">
|
||||
globe inside card: card@285 footer@1164 globe@1304 -> INSIDE the card, under the footer
|
||||
GET style.css?v=0.255.0 : 200, 93 551 bytes
|
||||
the .shell-lang rule it serves:
|
||||
.shell-lang { display: flex; justify-content: center; margin-top: 1.5rem; }
|
||||
old absolute rule gone: 0 occurrences
|
||||
```
|
||||
|
||||
Both halves of the defect are answered on the live box: the page now asks for a **versioned** URL, so
|
||||
a browser holding the pre-0.254.0 file fetches a new one; and the file it fetches carries the **new**
|
||||
`.shell-lang` rule, with the old viewport-absolute rule gone. The globe's markup sits inside the card
|
||||
and below the footer.
|
||||
|
||||
**What this does NOT prove:** how it LOOKS. `claude-in-chrome` is not available here, so the byte-level
|
||||
facts above are the whole of what a machine can check. The screenshot that found the bug is the kind
|
||||
of evidence that settles it — an operator click-through on `https://felhom.enkisfelhom.hu/login`.
|
||||
|
||||
**The guest share page** could not be fetched: there is no live share token on this box, so `/s/<x>`
|
||||
is a 404. Its change is the cache-buster only, and it is an **opaque tag** rather than the version —
|
||||
pinned by `TestShareGuest_HeadersTilesNoAdminChrome`, which refused the version when it was tried.
|
||||
|
||||
**State:** controller 0.255.0, language `hu`, 23 standing containers up, nothing installed or removed.
|
||||
@@ -0,0 +1,7 @@
|
||||
stylesheet link : <link rel="stylesheet" href="/static/style.css?v=0.255.0">
|
||||
globe inside card: card@285 footer@1164 globe@1304 -> INSIDE the card, under the footer
|
||||
GET style.css?v=0.255.0 : 200 93551 bytes
|
||||
the .shell-lang rule it serves:
|
||||
.shell-lang { display: flex; justify-content: center; margin-top: 1.5rem; }
|
||||
old absolute rule gone: 0
|
||||
guest share page link : 404 (404 as expected — no live share token)
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# v0.255.0 proof. Read-only: three GETs, no session needed.
|
||||
IP=172.17.0.2:8080; H="Host: felhom.enkisfelhom.hu"
|
||||
curl -s -H "$H" "http://$IP/login" -o /tmp/lg5.html
|
||||
echo -n " stylesheet link : "; grep -o '<link rel="stylesheet"[^>]*>' /tmp/lg5.html
|
||||
echo -n " globe inside card: "; python3 - <<'PY'
|
||||
import io
|
||||
s=io.open('/tmp/lg5.html',encoding='utf-8').read()
|
||||
c=s.find('class="login-card"'); g=s.find('class="shell-lang"'); f=s.find('class="login-footer"')
|
||||
print("card@%d footer@%d globe@%d -> %s"%(c,f,g, "INSIDE the card, under the footer" if c<f<g else "WRONG"))
|
||||
PY
|
||||
V=$(grep -o 'style.css?v=[^"]*' /tmp/lg5.html | sed 's/.*v=//')
|
||||
echo -n " GET style.css?v=$V : "; curl -s -o /tmp/css5 -w "%{http_code} %{size_download} bytes\n" -H "$H" "http://$IP/static/style.css?v=$V"
|
||||
echo " the .shell-lang rule it serves:"; grep -o '\.shell-lang *{[^}]*}' /tmp/css5 | head -2 | sed 's/^/ /'
|
||||
echo -n " old absolute rule gone: "; grep -c 'shell-lang { position: absolute' /tmp/css5
|
||||
echo -n " guest share page link : "; curl -s -H "$H" "http://$IP/s/nosuchtoken" -o /tmp/gs.html -w "%{http_code} " ; grep -o '<link rel="stylesheet"[^>]*>' /tmp/gs.html 2>/dev/null || echo "(404 as expected — no live share token)"
|
||||
@@ -0,0 +1,68 @@
|
||||
PARITY DIFF — controller v0.255.0, against the fixtures at 2e9d402 (v0.254.0)
|
||||
|
||||
A real (LCS) diff; the per-session CSRF token is blanked on both sides. Two things change and
|
||||
nothing else: the stylesheet gains its cache-buster, and the globe moves INSIDE the card.
|
||||
|
||||
[7 fixtures] recovery_locked_can.html, recovery_locked_cannot.html, recovery_locked_confirm.html, recovery_unlocked_apps.html, recovery_unlocked_empty.html, recovery_unlocked_unavailable.html, recovery_unlocked_untagged.html
|
||||
- <link rel="stylesheet" href="/static/style.css">
|
||||
+ <link rel="stylesheet" href="/static/style.css?v=0.247.0">
|
||||
- <div class="shell-lang"><details class="lang-globe">
|
||||
- <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
- <ul class="lang-globe-menu">
|
||||
- <li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="sub
|
||||
- <li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="sub
|
||||
- </ul>
|
||||
-</details></div>
|
||||
+ <div class="shell-lang"><details class="lang-globe">
|
||||
+ <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
+ <ul class="lang-globe-menu">
|
||||
+ <li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="sub
|
||||
+ <li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="sub
|
||||
+ </ul>
|
||||
+</details></div>
|
||||
|
||||
[4 fixtures] claim_reset_code.html, claim_reset_nocode.html, claim_setup_code.html, claim_setup_nocode.html
|
||||
- <link rel="stylesheet" href="/static/style.css">
|
||||
+ <link rel="stylesheet" href="/static/style.css?v=test">
|
||||
- <div class="shell-lang"><details class="lang-globe">
|
||||
- <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
- <ul class="lang-globe-menu">
|
||||
- <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item
|
||||
- <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item
|
||||
- </ul>
|
||||
-</details></div>
|
||||
+ <div class="shell-lang"><details class="lang-globe">
|
||||
+ <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
+ <ul class="lang-globe-menu">
|
||||
+ <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item
|
||||
+ <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item
|
||||
+ </ul>
|
||||
+</details></div>
|
||||
|
||||
[3 fixtures] launcher_share_password.html, launcher_shared_apps.html, launcher_shared_empty.html
|
||||
- <link rel="stylesheet" href="/static/style.css">
|
||||
+ <link rel="stylesheet" href="/static/style.css?v=7da11d7a">
|
||||
|
||||
[1 fixtures] login.html
|
||||
- <link rel="stylesheet" href="/static/style.css">
|
||||
+ <link rel="stylesheet" href="/static/style.css?v=0.250.0">
|
||||
- <div class="shell-lang"><details class="lang-globe">
|
||||
- <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
- <ul class="lang-globe-menu">
|
||||
- <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item
|
||||
- <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item
|
||||
- </ul>
|
||||
-</details></div>
|
||||
+ <div class="shell-lang"><details class="lang-globe">
|
||||
+ <summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
+ <ul class="lang-globe-menu">
|
||||
+ <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item
|
||||
+ <li><form method="POST" action="/lang"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item
|
||||
+ </ul>
|
||||
+</details></div>
|
||||
|
||||
[91 fixtures] IDENTICAL — EVERY dashboard page among them: zero re-captures outside the shells.
|
||||
|
||||
DISTINCT CHANGE SHAPES: 4 — the three shells that gained the moved globe AND the buster, and the
|
||||
two guest share pages, which gained ONLY the buster (they carry no globe) and take an OPAQUE tag
|
||||
rather than the version: a stranger holding a capability URL is not told which build is running.
|
||||
@@ -754,6 +754,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-576** | **[P3-LOW] `i18n_go_parity.py` cannot see a call site that LOST text; it only checks that the text a key carries is real.** FOUND 2026-09-18 by localisation slice 2 release B, and found the hard way: the bulk converter silently dropped the continuation of a multi-line concatenation (`fmt.Errorf("a: "+ "b: %s", x)` kept only `"a: "`), damaging **7** producers — and the gate stayed GREEN throughout, because every surviving fragment WAS a byte-equal base-commit literal. Its question ("is this text real?") was answered yes while the CALL had lost half its sentence and its arguments. Two behaviour tests caught it (`TestR356_ScenarioC_UndeployedAppIsStillRefused`, `TestR379_ScenarioA_RollbackSucceeds_AppComesBack`), because they assert the sentence a customer READS. **Fix shape:** the gate learns a second question — for every `util.MsgError("key", …)` call site, the count of its arguments must equal the count of printf verbs in the key's Hungarian value, and no key-naming literal may be adjacent to a `+`. Both are cheap and would have convicted all 7. **The general lesson, worth keeping whatever is built: a structural gate over the TEXT cannot see a defect in the CALL.** | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-577** | **[P3-LOW] A guest SHARE visitor has no way to pick a language, and the household's setting is the wrong default for them.** FOUND 2026-09-18 by localisation slice 2 release C (R-557, controller v0.254.0): every other page a person can reach now carries a language globe — the dashboard (the household's setting), and the sign-in and claim pages (the visitor's own cookie). The two guest share pages (`launcher_shared`, `launcher_share_password`) deliberately do NOT, and `TestGuestSharePagesHaveNoGlobe` pins that so it stays a decision rather than an oversight. **Why it is the operator's and not CC's:** a share visitor is a stranger the household sent a link to, and what language they are shown is a promise the SHARE FEATURE makes, not an implementation detail. The `felhom_lang` cookie already built would fit them exactly (display-only, their own browser, never the household's setting). **Fix shape, if the operator says yes:** add `{{template "lang_globe" .}}` to both shells with the anonymous form, and one render case per page per language. | **READY - rank P3-LOW; owner: operator (the decision), CC (the change)** |
|
||||
| **R-578** | **[P3-LOW] A helper that takes the settings lock must never be called from inside a settings callback — there is no gate, only one test in one package.** FOUND 2026-09-18 the hard way, by localisation slice 2 release C introducing exactly that: `UpdateOffboxStatus` holds the settings WRITE lock while it runs its callback, `boxLang()` reads the language through the READ lock, and `sync.RWMutex` is not reentrant — so the off-site run's final status write DEADLOCKED, **holding the settings lock**, which would wedge everything else on that box that touches `settings.json`. The only symptom was `go test ./internal/backup/` going from 8 minutes to a 25-minute timeout. Fixed by hoisting the language resolution; `TestNoteHelpersAreNotCalledUnderTheSettingsLock` (internal/backup) now names the file and line in a second. **What is still open:** that test covers `internal/backup` only, and it knows only the `note`/`noteErr`/`boxLang` helpers. Any other settings-reading helper, in any other package, can make the same mistake with nothing to catch it but a hang. **Fix shape:** promote it to a gate over every package, keyed on "a call to a method that reads settings, inside a literal passed to a `settings.Update*` function"; or give `Settings` a re-entrant read path and remove the class. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-579** | **[P3-LOW] Five page shells loaded `style.css` with NO cache-buster, so a browser holding an older copy kept being served CSS that did not know about the newest UI.** FOUND 2026-09-18 by the operator's screenshot of the v0.254.0 language globe: it rendered as a bare, unstyled `<details>` — a stray triangle and two plain words outside the card — because `login.html`, `claim.html`, `recovery.html`, `launcher_shared.html` and `launcher_share_password.html` requested `/static/style.css` with no `?v=`, while `layout.html` has used `?v={{.Version}}` since v0.166.0. **`.Version` was also absent from three of those five data maps.** FIXED AND CLOSED in the same session (controller v0.255.0): the parameter on all five, and `Version` set once in `executeTemplateLang` so a new shell cannot miss it; `TestGlobeOnAnonymousShells` now refuses an absent or EMPTY `?v=`. **The general form, which is the part worth keeping: a template that loads a versioned asset WITHOUT its version is invisible to every test that reads markup — the markup is correct and the browser fetches the wrong file.** A gate over "every stylesheet/script link in a template carries `?v=`" would catch the class; not built, because 8 first-boot-wizard templates would fail it and R-554 deletes them. | **CLOSED 2026-09-18 - controller v0.255.0** |
|
||||
| **R-537** | **[P1-HIGH] The app-backup page labels the tier-1 backup „DB + Konfig + Adatok" and prints the app's data-drive size next to it — but the tier-1 unit contains NO drive-side app data at all.** MEASURED 2026-09-16 on the drill box (fresh install, controller 0.243.0, one drive, tier 2 and tier 3 both „Nincs beállítva"): five photos (3 000 000 B) were uploaded into Nextcloud through its own WebDAV interface, then the customer-visible „Mentés most" was pressed (`POST /api/backup/run` → 200, the unit grew 25 337 B → 978 MB). The resulting unit's `manifest.json` lists `db-dumps` + three **docker volume** dumps and nothing else; listing the 781 MB `nextcloud_nextcloud_html.tar` (29 346 entries, positive control `version.php` = 3 hits) gives **`Fotok` = 0 and `nyaralas` = 0**, and `./data/` is the empty bind-mount point. A `find` over the whole `backups/` tree for `*appdata*` / `*Fotok*` returns nothing. The page nevertheless renders „1. mentés … DB + Konfig + Adatok" and „Nextcloud Adatlemez 65.1 MB" — a size measured on exactly the data it does not copy (`internal/web/handlers.go:1176-1178`, `BackupContents`). **This is a truth defect, not a design defect:** `07-backup-architecture.md` §6.2 places nextcloud's file leg at **Tier 2 and Tier 3 only**, and its „[FACT] What the whole-guest tiers do NOT carry" says `mp8 /mnt/felhom-drives` is out of vzdump scope (confirmed live: „excluding bind mount point mp8 … (not a volume)"). So on a one-drive box with no off-site tier — the state every fresh install starts in — the household's files are in **no backup**, while the page says „Adatok". Same family as R-517/R-518. **Fix shape:** render tier-1 contents from the capture set actually written (`ComputeCaptureSet`), so a unit with no file leg reads „DB + Konfig" and the drive size is not shown beside it; and say on the page that the app's files need tier 2 or tier 3. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** The contents label is computed PER TIER from what that tier captures: Tier 1 says „Adatok" only when the app's data really is in the volumes the unit captured, and a class-A app carries one sentence saying where its files ARE protected. Proven on demo-hp through the page the customer opens: Paperless-ngx reads „1. mentés … DB + Konfig" with „Az alkalmazás fájljait a távoli másolat (és a második meghajtó) védi …", while its „2. mentés" row still reads „DB + Konfig + Adatok". Red-proof: restoring the old app-shaped label fails `TestAppBackupRows_Tier1LabelDoesNotClaimFilesItCannotHold`. **RE-PROVEN 2026-09-16 on a FRESH box** (installed from the built ISO 1.28.0, controller 0.244.0, off-site on by default): the Nextcloud row read „1. mentés … DB + Konfig" with the new sentence, „2. mentés … Nincs 2. (off-drive) másolat", „3. mentés Sikeres restic → …your-storagebox.de"; „DB + Konfig + Adatok" appeared ZERO times while the local unit held no file leg. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** |
|
||||
| **R-538** | **[P1-HIGH] A tier-1 app restore reports plain success and leaves Nextcloud listing files whose bytes were never in the backup — and it destroys the app's own trash, the customer's last copy.** MEASURED 2026-09-16 on the drill box, F10 („a child deletes the photo folder"): the five photos were deleted through Nextcloud (DELETE 204, PROPFIND 404), then restored through the page exactly as a customer would (`POST /backup/restore` `stack_name=nextcloud` `snapshot_id=helyi` → 302, finished in **35 s**, „A(z) nextcloud: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult."). Afterwards the folder is back and **lists all five photos**, and **none of them opens**: `GET nyaralas-1..5` = 404 / 503×4 with `Sabre\DAV\Exception\NotFound`, while the positive controls at the same moment pass (`status.php` 200, WebDAV PUT 201, GET 200). Cause: the replayed MariaDB dump (11:01:45Z) knows the photos, the bytes live on `mp8` and were never captured (R-537). **Worse:** the bytes were still on the drive in Nextcloud's own trash (`appdata/nextcloud/admin/files_trashbin/files/Fotok.d1789556707/nyaralas-1..5.jpg`, all five present) and the restored database no longer references them — the trash listing comes back **empty**, so „restore from trash", the one route that would have worked, is gone. The customer is left with five unopenable photos, a success message, and no warning. **Fix shape:** before replaying a database whose app has an uncaptured file leg, refuse or warn („ennek az alkalmazásnak a fájljai nincsenek ebben a mentésben — a visszaállítás után a fájlok hiányozni fognak"); and never present a DB-only restore of a class-A app as a complete one. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** A unit restore refuses before anything is touched when the unit cannot return the app's drive-side files, and names the route that can. Fired live on demo-hp: `POST /backup/restore` for paperless-ngx → 302 with „Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza …", and the app read `running` before AND after, so nothing was stopped and no trash was made unreachable. The database-and-settings-only path exists as a separately worded second step. Red-proof: disabling the guard fails `TestUnitRestore_RefusesWhenTheUnitCannotHoldTheFiles`. **RE-PROVEN 2026-09-16 on a FRESH box, and this time the refusal had somewhere to point:** after five photos were deleted, `POST /backup/restore` was refused with „…a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: … „Teljes visszaállítás (fájlok + adatbázis)"", the app read `running` before AND after, and the wastebasket was untouched. The off-site route then returned all five photos — 200 with the exact uploaded sizes and sha256 IDENTICAL to the originals, 5/5, with a negative control. Evidence: `audits/evidence-backup-promise-2026-09-16/phaseE-photos.txt`. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** |
|
||||
| **R-525** | **[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)** |
|
||||
|
||||
Reference in New Issue
Block a user