docs: localisation slice 2 release B (controller v0.253.0) — R-557 progress, R-575, R-576
gates / gates (push) Successful in 25s
gates / gates (push) Successful in 25s
All 179 Hungarian error messages carry a key; zero remain. 10-localisation.md gains §10.2: the
four properties util.MsgError had to have at once and the failure each one prevents, and the
plural rule as a BUNDLE rule rather than a per-call-site flag, with the answerable sentence and
what the other option would have cost.
Two instrument defects recorded rather than tidied away, because both shapes recur:
R-576 — the parity gate has a measured blind spot. The bulk converter dropped the continuation
of multi-line concatenations, damaging 7 producers, and the gate stayed GREEN: every surviving
fragment WAS a byte-equal base-commit literal, so its question ("is this text real?") was
answered yes while the CALL had lost half its sentence. Two behaviour tests caught it. The
general form: a structural gate over the TEXT cannot see a defect in the CALL.
And the script counting what was left was case-sensitive, so it said "0 remain" while five did —
R-565's shape inside the measurement. Every "no Hungarian left" claim in this slice is now made
case-insensitively and with both controls.
R-575 — the soft memory-overcommit warning has no error to carry a key and no language where it
is built, so it renders Hungarian on an English page. Named in the code, not hidden.
Live evidence includes a mistake I made and corrected: a probe of the deploy refusal INSTALLED
vaultwarden on demo-hp (the endpoint accepts before it validates), the same mistake the previous
session recorded. Removed through the product's own path with its data; verified gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,46 +1,49 @@
|
||||
# STATUS — what works, what's broken, what's next
|
||||
|
||||
**Updated 2026-09-18 — the sentences the program writes now follow the language.**
|
||||
**Updated 2026-09-18 (evening) — the sentences the program writes now follow the language, error messages included.**
|
||||
|
||||
> **Ready for a volunteer: yes, unchanged.** A Hungarian household sees exactly what it saw
|
||||
> yesterday. I did not read the pages to decide that — I compared them, letter by letter.
|
||||
|
||||
**Decisions I took.** None. One choice you were asked for stayed on its own stated default: notes
|
||||
saved to disk at night (last error, last warning) will be written in the box's language at the time
|
||||
they are written. A household that switches language sees last night's note in the old language until
|
||||
the next run. That is option 1 in the plan, and the plan says it is what happens if nobody decides.
|
||||
**Decisions I took.** None you have to reverse. One choice you were asked for stayed on its own
|
||||
stated default: notes saved to disk overnight will be written in the box's language at the time they
|
||||
are written, so a household that switches sees last night's note in the old language until the next
|
||||
run. That is what the plan says happens if nobody decides.
|
||||
|
||||
**What I exercised, on the demo HP box.** The pages were already English. The SENTENCES the program
|
||||
writes into them were not — "Saved.", "The remote target is not set.", the country names, the yellow
|
||||
banners at the top. I moved 226 of them, and they now follow the language. Two I want to name:
|
||||
**What I did.** Two releases. The first moved the sentences the program writes into the pages — the
|
||||
little green line after you press a button, the yellow banners, the country names, the answers the
|
||||
page fetches in the background. The second did the **error messages**: about 180 sentences that are
|
||||
written deep inside the program and printed by whatever catches them. All of them now travel with a
|
||||
short code, so the page that shows them writes them in your language. **None are left in Hungarian
|
||||
only.**
|
||||
|
||||
- **The little green "done" line after you press a button.** It travels inside the web address, so
|
||||
it is written by one page and read by the next. It used to be written in whoever's language came
|
||||
first. It now travels as a short code and the page you land on writes the sentence. A link you
|
||||
already have open from before still shows its old sentence, word for word — I checked that live.
|
||||
- **The country list.** All 237 names, and the list re-sorts itself, so the English list really is in
|
||||
English alphabetical order and not Hungarian order with English words in it.
|
||||
**My mistake, and what I did about it.** My first test of an error message asked the box to install an
|
||||
app with nothing filled in, expecting a refusal — and it installed the app instead, because that
|
||||
particular one needs nothing filled in. **This is the same mistake the session before mine made and
|
||||
wrote down, and I repeated it.** I stopped the app and removed it the way a customer would, with its
|
||||
data, and checked: no container, no folder, no leftover storage. The 23 apps that should be running
|
||||
are all running. The real lesson is sharper than "pick a different app": that button accepts first and
|
||||
checks afterwards, so it cannot be used to test a refusal at all. The tests were rewritten to use
|
||||
requests that are refused before anything is created.
|
||||
|
||||
**How I know Hungarian did not change.** A new check freezes every Hungarian sentence in the program
|
||||
as it stood before I started — 7 467 of them — and refuses any sentence that is not exactly that,
|
||||
down to a comma. It caught a real mistake in itself on the first run and I fixed the check. Then on
|
||||
the box: ten Hungarian pages before and after. Six identical. The other four differ only in things
|
||||
that move on their own — a clock ticking over, an app that was unhealthy for a minute, and the
|
||||
"update available" line, which is true on the old version and not on the new one.
|
||||
**How I know Hungarian did not change.** A check freezes every Hungarian sentence as it stood before I
|
||||
started — 7 467 of them — and refuses any that is not exactly that, down to a comma. Then on the box:
|
||||
ten Hungarian pages before and after, six identical, the other four differing only in a clock ticking
|
||||
over, an app unhealthy for a minute, and the "update available" line.
|
||||
|
||||
**What I deliberately did NOT translate.** Anything the central service reads and turns into an
|
||||
e-mail to the household. I checked, and found the plan was wrong about this: the service does not
|
||||
always write its own e-mail — for several kinds of message it sends the box's own sentence straight
|
||||
on. Translating those here would have changed an e-mail nobody asked to change. They are now frozen
|
||||
by a test, and they get their turn in the next step.
|
||||
**Two things went wrong in my own tools, and I am naming them rather than tidying them away.** My
|
||||
bulk converter quietly dropped the second half of sentences that were written across two lines — seven
|
||||
of them — and **the freeze-check did not notice**, because each surviving half really was a real old
|
||||
sentence. What caught it was two tests that read the sentence a customer would see. And my counting
|
||||
script ignored capital letters, so it told me "none left" while five were. Both are written down with
|
||||
a number.
|
||||
|
||||
**What is not done yet.** About 900 sentences remain. 176 of them are error messages, which is the
|
||||
next release; the rest are notes saved to disk, which is the one after. Four smaller gaps I found
|
||||
while working are written down with a number each.
|
||||
**What is left.** The notes saved to disk overnight — one more release. Plus a handful of small gaps,
|
||||
each with a number.
|
||||
|
||||
**Rows.** One closed, three opened. The register went from **264** to **266** open rows.
|
||||
**Rows.** One closed, five opened across the day. The register went from **264** to **269** open rows.
|
||||
|
||||
**Needs you.** Nothing. The new version runs on the demo HP box only; the fleet floor is unchanged at
|
||||
**Needs you.** Nothing. Both new versions run on the demo HP box only; the fleet floor is unchanged at
|
||||
0.250.0. If you do nothing: the other boxes keep the version they have, and nothing breaks.
|
||||
|
||||
---
|
||||
|
||||
@@ -279,7 +279,7 @@ session). Each slice ends with the parity gate green for every page it touched.
|
||||
|---|---|---|---|---|
|
||||
| 0 (done) | — | mechanism, 3 pages + layout, setting, report field, gates | Hungarian unchanged by measurement; English reachable | spent |
|
||||
| 1 (done, v0.248.0–v0.250.0) | R-556 | the other 31 dashboard templates (~1 500 strings, 600 of them JS), parity fixtures per page; switch shown to everyone; English retrieval stems; the extractor's ASCII word list reviewed per page | the whole dashboard in English with Hungarian byte-identical | 12–16 h, three releases |
|
||||
| 2 | R-557 (after R-553) | Go-side customer strings; flash-in-URL → keys; country names; alert texts | a page's server messages follow the language | 16–20 h, three releases · **A shipped 2026-09-18** |
|
||||
| 2 | R-557 (after R-553) | Go-side customer strings; flash-in-URL → keys; country names; alert texts; the 179 error messages | a page's server messages follow the language | 16–20 h, three releases · **A and B shipped 2026-09-18; C remains** |
|
||||
| 3 | R-558 | hub: per-customer language at creation; `configgen` renders it; `customerMessages` (39), the 5 lifecycle mails and the self-bind page in English; dispatcher reads the reported language | a household's e-mails arrive in its language | 6–8 h, one hub + one controller release |
|
||||
| 4 | R-559 | console banner (34 lines, console-font limits) and the download page | an English household meets English from the first boot screen (in scope — ruling 1b) | 4–6 h + an ISO train |
|
||||
| 5 | R-560 | catalog: §7 format, controller reads it, 835 strings / ~5 000 words, catalog copy gates per language | an app card, its settings and its first steps in English | 12–16 h |
|
||||
@@ -350,6 +350,50 @@ night's note in the old language until the next run rewrites it. The alternative
|
||||
live) costs a dozen new persisted fields and a legacy path for each — the R-570 shape, a dozen times
|
||||
over. Recorded here when release C ships.
|
||||
|
||||
### 10.2 Slice 2 release B — an error carries the key of the sentence it is (v0.253.0)
|
||||
|
||||
**[DESIGN] An error is made where there is no language and printed where there is no key, so it
|
||||
carries the key across.** `util.MsgError(key, args…)` / `MsgErrorf(kind, key, args…)`. **All 179
|
||||
Hungarian error literals are converted; zero remain** (an ASCII-fragment search with a positive and a
|
||||
negative control, case-insensitively, plus an accented-letter search).
|
||||
|
||||
Four properties, each one a different failure if it is missing:
|
||||
|
||||
| property | the failure without it | pinned by |
|
||||
|---|---|---|
|
||||
| `Error()` is the Hungarian, byte for byte | 179 producers could not be converted until every printer was, in one commit | `TestMsgErrorKeepsKindAndHuText` |
|
||||
| `errors.Is` answers for the kind **and** a wrapped cause | `KindErrorf` returned the kind alone; a caller testing for the cause silently stops matching | `TestMsgErrorUnwrapsTheCauseToo` |
|
||||
| an error ARGUMENT renders recursively | „formázás sikertelen: %w" is a sentence wrapping a sentence; half of it would stay Hungarian | `TestErrTextRendersAWrappedMessageErrorToo` |
|
||||
| a FOREIGN error prints verbatim | restic, docker, ssh and the stdlib are not ours (§9's rule) | `TestErrTextFallsBackVerbatim` |
|
||||
|
||||
**[DESIGN] Plurals are a BUNDLE rule, not a call-site flag.** *A key that carries `.one`/`.other`
|
||||
forms in a language is a plural key, and its FIRST parameter is the count* (`i18n.Bundle.form`).
|
||||
Hungarian never carries them, so a Hungarian render is unchanged at every count. One answerable
|
||||
sentence: *where does the fact that English needs two forms live?* Options: (1) a flag at every
|
||||
producer of every count message, in three packages — cost: the one somebody forgets reads „3 app is
|
||||
not running", with nothing to catch it; (2) the bundle. **Chosen (2)**: the bundle is where a
|
||||
translator works, and it needs no change at any call site. `.one`/`.other` become RESERVED suffixes —
|
||||
`TestNoOrdinaryKeyEndsInAPluralSuffix`, which caught a real collision (`alert.deadapp.one`) the day
|
||||
the rule landed.
|
||||
|
||||
**[FACT] The parity gate has a blind spot, measured rather than reasoned (R-576).** The bulk
|
||||
converter silently dropped the continuation of a multi-line concatenation, damaging **7** producers —
|
||||
and `i18n_go_parity.py` stayed GREEN, because every surviving fragment WAS a byte-equal base-commit
|
||||
literal. Its question is *"is this text real?"*; it cannot ask *"did the call keep all of it?"*. Two
|
||||
BEHAVIOUR tests caught it, because they assert the sentence a customer reads. **A structural gate over
|
||||
the TEXT cannot see a defect in the CALL** — the general form, and the reason a rendered test is not
|
||||
made redundant by a gate.
|
||||
|
||||
**[FACT] A second instrument defect, same session (recorded because the shape recurs).** The script
|
||||
counting what was left was case-sensitive, so it reported "0 error literals remain" while five did
|
||||
(„occ parancs sikertelen", „hub hiba", „OnlyOffice aldomain nem ismert" ×2). That is R-565's shape
|
||||
inside the measurement. Every "no Hungarian left" claim in this slice is made case-insensitively and
|
||||
with both controls.
|
||||
|
||||
**[FACT] One gap release B could not close: R-575.** `memoryVerdict`'s soft overcommit WARNING is a
|
||||
string with no error to carry a key and no language where it is built, so it renders Hungarian on an
|
||||
English page. The copy is in the bundle; only the render is fixed. Named in the code, filed as a row.
|
||||
|
||||
---
|
||||
|
||||
## 11. Operator decisions
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Live validation — controller v0.253.0 on demo-hp guest 9201 (2026-09-18, release B)
|
||||
|
||||
**Method: endpoint-level, stated as such.** No browser on DooPlex. Session from a real `POST /login`;
|
||||
`X-CSRF-Token` from the page meta for API calls; the password passed as a file and deleted from host
|
||||
and guest afterwards.
|
||||
|
||||
## 0. A mistake I made, first — an app was installed on the demo box and removed
|
||||
|
||||
My first release-B probe posted `/api/stacks/vaultwarden/deploy` with empty values, expecting the
|
||||
required-field refusal. **The deploy API answers 202 and validates ASYNCHRONOUSLY**, and vaultwarden
|
||||
has no required field — so it installed. **That is the same mistake the 2026-09-17 session recorded
|
||||
in STATUS.md, repeated.** It is not a trap in the product; it is a trap in the probe, and the lesson
|
||||
is narrower than "pick a different app": **the deploy endpoint cannot be probed for a refusal at all,
|
||||
because it accepts before it validates.**
|
||||
|
||||
Removed through the product's own path — stop, then `POST /remove` with `remove_hdd_data` and
|
||||
`remove_backups` — and verified gone: no container, no `/opt/felhom/stacks/vaultwarden`, zero
|
||||
`vaultwarden` volumes. The 23 standing containers are all up. The removal answered
|
||||
`hdd_note: „Az alkalmazás nem tárolt saját adatot külső meghajtón…"`, so nothing was on a drive.
|
||||
|
||||
The probe was rewritten to use only endpoints whose validator runs BEFORE any mutation
|
||||
(`probeB2.sh`, `probeB3.sh`).
|
||||
|
||||
## 1. An error made deep in a package, rendered on the page in each language
|
||||
|
||||
`POST /sharing/shares` with a bad name. The message is produced in `internal/settings/smb.go`, three
|
||||
layers below the handler, and the redirect is FOLLOWED so what is read is what the page shows.
|
||||
|
||||
| refusal | hu | en |
|
||||
|---|---|---|
|
||||
| name has a slash | „a megosztás neve nem tartalmazhat perjelet vagy pontot" | "the share name cannot hold a slash or a dot" |
|
||||
| name empty | „a megosztás neve nem lehet üres" | "the share name cannot be empty" |
|
||||
| name longer than 15 | „a megosztás neve legfeljebb 15 karakter lehet" | "the share name can be at most 15 characters" |
|
||||
| name starts with `_` | „a megosztás neve nem kezdődhet aláhúzással — ezek a nevek a rendszernek vannak fenntartva" | "the share name cannot start with an underscore — those names are reserved for the system" |
|
||||
|
||||
Every Hungarian line is the literal that stood in `smb.go` before the conversion, byte for byte.
|
||||
|
||||
## 2. The compatibility case, still true after B
|
||||
|
||||
`/sharing?flash=Be%C3%A1ll%C3%ADt%C3%A1s+mentve.` — a URL an older controller minted — renders
|
||||
„Beállítás mentve." in **both** languages. Shown verbatim, never as a key, never dropped.
|
||||
|
||||
## 3. What stays English on purpose, confirmed live
|
||||
|
||||
`POST /api/stacks/felhom-controller/remove` → `403 stack "felhom-controller" is protected and cannot
|
||||
be removed`. Internal English, identical in both languages: **R-569**, not this slice. Recorded here
|
||||
so it is not read as a gap release B left.
|
||||
|
||||
An agent-side refusal (`/api/disks/format` on a device that is not whitelisted) comes back as
|
||||
`agentapi: format: HTTP 400: storage: refusing to operate on non-whitelisted block device` — the
|
||||
AGENT's sentence, relayed verbatim in both languages. That is the rule working: text this controller
|
||||
did not write is text it does not translate.
|
||||
|
||||
## 4. State left behind
|
||||
|
||||
Controller **0.253.0**; saved language **`hu`** (every English probe used the `?lang=en` override,
|
||||
which is not persisted); 23 standing containers up, unchanged; **vaultwarden installed by mistake and
|
||||
fully removed**; no drive touched, no floor raised, no golden baked.
|
||||
@@ -0,0 +1,19 @@
|
||||
LOGIN OK
|
||||
##### lang=hu
|
||||
1 disks/assign, mount name with a slash (internal/web validator): {"error":"uuid and where are required","ok":false}
|
||||
[400]
|
||||
2 disks/format, unsupported filesystem (internal/web validator): {"error":"agentapi: format: HTTP 400: storage: refusing to operate on non-whitelisted block device \"/dev/zzz\"","ok":false}
|
||||
[500]
|
||||
3 share name with a slash (internal/settings/smb.go validator): err.settings.a_megosztas_neve_nem_tartalmazhat_perjelet
|
||||
4 share name empty (internal/settings/smb.go validator): err.settings.a_megosztas_neve_nem_lehet_ures
|
||||
5 remove a PROTECTED stack (internal/stacks, English by design): {"ok":false,"error":"stack \"felhom-controller\" is protected and cannot be removed"}
|
||||
[403]
|
||||
##### lang=en
|
||||
1 disks/assign, mount name with a slash (internal/web validator): {"error":"uuid and where are required","ok":false}
|
||||
[400]
|
||||
2 disks/format, unsupported filesystem (internal/web validator): {"error":"agentapi: format: HTTP 400: storage: refusing to operate on non-whitelisted block device \"/dev/zzz\"","ok":false}
|
||||
[500]
|
||||
3 share name with a slash (internal/settings/smb.go validator): err.settings.a_megosztas_neve_nem_tartalmazhat_perjelet
|
||||
4 share name empty (internal/settings/smb.go validator): err.settings.a_megosztas_neve_nem_lehet_ures
|
||||
5 remove a PROTECTED stack (internal/stacks, English by design): {"ok":false,"error":"stack \"felhom-controller\" is protected and cannot be removed"}
|
||||
[403]
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# Release B probe, SAFE BY CONSTRUCTION: every request below is refused by a validator that runs
|
||||
# BEFORE any mutation. Nothing is installed, created, formatted or deleted.
|
||||
IP=172.17.0.2:8080
|
||||
H="Host: felhom.enkisfelhom.hu"
|
||||
PW=$(cat /tmp/.felhompw); J=/tmp/pj.txt; rm -f $J
|
||||
curl -s -c $J -H "$H" "http://$IP/login" -o /tmp/lg.html
|
||||
CSRF=$(grep -o 'name="_csrf" value="[^"]*"' /tmp/lg.html | head -1 | sed 's/.*value="//;s/"//')
|
||||
SESS=$(curl -s -b $J -H "$H" -D - -o /dev/null -X POST "http://$IP/login" --data-urlencode "password=$PW" --data-urlencode "_csrf=$CSRF" | grep -i '^set-cookie: felhom_session' | head -1 | sed 's/[Ss]et-[Cc]ookie: //;s/;.*//')
|
||||
[ -z "$SESS" ] && { echo "LOGIN FAILED"; exit 1; }
|
||||
curl -s -H "$H" -H "Cookie: $SESS" "http://$IP/launcher" > /tmp/lp.html
|
||||
MC=$(grep -o 'name="csrf-token" content="[^"]*"' /tmp/lp.html | head -1 | sed 's/.*content="//;s/"//')
|
||||
SC=$(grep -o 'name="_csrf" value="[^"]*"' /tmp/lp.html | head -1 | sed 's/.*value="//;s/"//')
|
||||
echo "LOGIN OK"
|
||||
for L in hu en; do
|
||||
echo "##### lang=$L"
|
||||
JP(){ curl -s -H "$H" -H "Cookie: $SESS" -H "X-CSRF-Token: $MC" -H 'Content-Type: application/json' --max-time 12 -X POST "http://$IP$1" -d "$2" -w " [%{http_code}]"; }
|
||||
FP(){ curl -s -H "$H" -H "Cookie: $SESS" --max-time 12 -o /dev/null -w "%{redirect_url}" -X POST "http://$IP$1" --data-urlencode "_csrf=$SC" "${@:2}"; }
|
||||
echo -n " 1 disks/assign, mount name with a slash (internal/web validator): "
|
||||
JP "/api/disks/assign?lang=$L" '{"device":"/dev/zzz","name":"bad/name"}' | head -c 200; echo
|
||||
echo -n " 2 disks/format, unsupported filesystem (internal/web validator): "
|
||||
JP "/api/disks/format?lang=$L" '{"device":"/dev/zzz","name":"probe","fstype":"btrfs"}' | head -c 200; echo
|
||||
echo -n " 3 share name with a slash (internal/settings/smb.go validator): "
|
||||
FP "/sharing/shares?lang=$L" --data-urlencode "name=bad/name" --data-urlencode "path=/tmp" | sed 's/.*flash[^=]*=//' | head -c 200; echo
|
||||
echo -n " 4 share name empty (internal/settings/smb.go validator): "
|
||||
FP "/sharing/shares?lang=$L" --data-urlencode "name=" --data-urlencode "path=/tmp" | sed 's/.*flash[^=]*=//' | head -c 200; echo
|
||||
echo -n " 5 remove a PROTECTED stack (internal/stacks, English by design): "
|
||||
JP "/api/stacks/felhom-controller/remove?lang=$L" '{}' | head -c 200; echo
|
||||
done
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# Release B, the sentence AS RENDERED. Every request is refused by a validator that runs before any
|
||||
# mutation; the redirect is then FOLLOWED so what is read is what the customer sees on the page.
|
||||
IP=172.17.0.2:8080
|
||||
H="Host: felhom.enkisfelhom.hu"
|
||||
PW=$(cat /tmp/.felhompw); J=/tmp/pj.txt; rm -f $J
|
||||
curl -s -c $J -H "$H" "http://$IP/login" -o /tmp/lg.html
|
||||
CSRF=$(grep -o 'name="_csrf" value="[^"]*"' /tmp/lg.html | head -1 | sed 's/.*value="//;s/"//')
|
||||
SESS=$(curl -s -b $J -H "$H" -D - -o /dev/null -X POST "http://$IP/login" --data-urlencode "password=$PW" --data-urlencode "_csrf=$CSRF" | grep -i '^set-cookie: felhom_session' | head -1 | sed 's/[Ss]et-[Cc]ookie: //;s/;.*//')
|
||||
[ -z "$SESS" ] && { echo "LOGIN FAILED"; exit 1; }
|
||||
curl -s -H "$H" -H "Cookie: $SESS" "http://$IP/sharing" > /tmp/sp.html
|
||||
SC=$(grep -o 'name="_csrf" value="[^"]*"' /tmp/sp.html | head -1 | sed 's/.*value="//;s/"//')
|
||||
echo "LOGIN OK"
|
||||
# $1 label, $2 form args...
|
||||
try(){ local label="$1"; shift
|
||||
for L in hu en; do
|
||||
LOC=$(curl -s -H "$H" -H "Cookie: $SESS" --max-time 12 -o /dev/null -w "%{redirect_url}" \
|
||||
-X POST "http://$IP/sharing/shares" --data-urlencode "_csrf=$SC" "$@")
|
||||
# the URL the browser is sent to, then the page as it renders it
|
||||
Q=$(echo "$LOC" | sed 's|.*/sharing?||')
|
||||
TXT=$(curl -s -H "$H" -H "Cookie: $SESS" --max-time 12 "http://$IP/sharing?${Q}&lang=$L" \
|
||||
| grep -o 'alert alert-success">[^<]*' | head -1 | sed 's/.*">//')
|
||||
printf " %-34s %s -> %s\n" "$label" "$L" "$TXT"
|
||||
done
|
||||
}
|
||||
try "share name with a slash" --data-urlencode "name=bad/name" --data-urlencode "path=/tmp"
|
||||
try "share name empty" --data-urlencode "name=" --data-urlencode "path=/tmp"
|
||||
try "share name too long (>15)" --data-urlencode "name=aaaaaaaaaaaaaaaaaaaa" --data-urlencode "path=/tmp"
|
||||
try "share name starts with _" --data-urlencode "name=_reserved" --data-urlencode "path=/tmp"
|
||||
echo "--- and the legacy case: a URL an OLD controller minted, carrying prose ---"
|
||||
for L in hu en; do
|
||||
TXT=$(curl -s -H "$H" -H "Cookie: $SESS" --max-time 12 "http://$IP/sharing?flash=Be%C3%A1ll%C3%ADt%C3%A1s+mentve.&lang=$L" | grep -o 'alert alert-success">[^<]*' | head -1 | sed 's/.*">//')
|
||||
printf " %-34s %s -> %s\n" "legacy prose in the URL" "$L" "$TXT"
|
||||
done
|
||||
@@ -0,0 +1,12 @@
|
||||
LOGIN OK
|
||||
share name with a slash hu -> a megosztás neve nem tartalmazhat perjelet vagy pontot
|
||||
share name with a slash en -> the share name cannot hold a slash or a dot
|
||||
share name empty hu -> a megosztás neve nem lehet üres
|
||||
share name empty en -> the share name cannot be empty
|
||||
share name too long (>15) hu -> a megosztás neve legfeljebb 15 karakter lehet
|
||||
share name too long (>15) en -> the share name can be at most 15 characters
|
||||
share name starts with _ hu -> a megosztás neve nem kezdődhet aláhúzással — ezek a nevek a rendszernek vannak fenntartva
|
||||
share name starts with _ en -> the share name cannot start with an underscore — those names are reserved for the system
|
||||
--- and the legacy case: a URL an OLD controller minted, carrying prose ---
|
||||
legacy prose in the URL hu -> Beállítás mentve.
|
||||
legacy prose in the URL en -> Beállítás mentve.
|
||||
@@ -24,3 +24,31 @@ Each line: what was broken, what convicted, restored green after.
|
||||
7. internal/web — a Server with no bundle field renders raw keys
|
||||
planted: s.bundle() returns s.i18n unchanged (no i18n.Shared fallback)
|
||||
CONVICTS: TestFlashOnAServerWithNoBundleField — showed "flash.share.enabled"
|
||||
|
||||
RELEASE B (v0.253.0), 2026-09-18 — errors carry a key
|
||||
|
||||
8. util.msgError.Error() returns ENGLISH instead of Hungarian
|
||||
CONVICTS: TestMsgErrorKeepsKindAndHuText — "%v printed 'That setting is not valid: …'"
|
||||
9. Unwrap() returns the kind ALONE (the old KindErrorf behaviour)
|
||||
CONVICTS: TestMsgErrorUnwrapsTheCauseToo — "the wrapped CAUSE is unreachable"
|
||||
10. ErrText rewrites a FOREIGN error (restic/docker/stdlib) instead of passing it through
|
||||
CONVICTS: TestErrTextFallsBackVerbatim, both languages
|
||||
11. an inner error argument is pre-rendered in Hungarian instead of recursively
|
||||
CONVICTS: TestErrTextRendersAWrappedMessageErrorToo
|
||||
12. err.Error() put back at a converted display sink (storage_handlers.go:822)
|
||||
CONVICTS: TestNoErrErrorInPageOutput, naming the file, the line and the call
|
||||
13. the English plural collapsed to the singular (alert.deadapp.group.other)
|
||||
CONVICTS: TestPluralEnglish — "4 installed app is not running"
|
||||
14. Hungarian GIVEN a plural form it must not have
|
||||
CONVICTS: TestPluralEnglish — "the Hungarian sentence moved"
|
||||
|
||||
TWO LIVE CATCHES IN RELEASE B — neither planted:
|
||||
A. The bulk converter SILENTLY DROPPED the continuation of a multi-line concatenation
|
||||
(`fmt.Errorf("a: "+ "b: %s", x)` kept only "a: "), damaging 7 producers. Found by
|
||||
TestR356_ScenarioC and TestR379_ScenarioA, which assert the SENTENCE a customer reads.
|
||||
The parity gate did NOT catch it: every surviving fragment was byte-equal to a base literal,
|
||||
so the gate's question ("is this text real?") was answered yes while the CALL had lost text.
|
||||
All 7 rebuilt as joined keys; the six wrong keys pruned from both bundles.
|
||||
B. My own counting script was CASE-SENSITIVE, so it reported "0 error literals left" while five
|
||||
remained ("occ parancs sikertelen", "hub hiba", "OnlyOffice aldomain nem ismert" x2). The
|
||||
R-565 shape, in the instrument. Re-measured with re.I; the five are converted.
|
||||
|
||||
@@ -733,7 +733,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-552** | **[P3-LOW] An interrupted-restore notice for an app that is then REMOVED stays on the restore page for ever.** FOUND 2026-09-17 by CC reviewing its own controller v0.246.0 (R-550) during live validation. The per-app notice (`Manager.opInterrupted`, persisted in `restore-status.json`) is cleared in exactly one place — `BeginRestoreOp` for that app (`internal/backup/opstatus.go`) — and `removeStack` (`internal/api/router.go`) never touches the restore record. So a household that answers „A visszaállítás megszakadt … indítsd el újra" by REMOVING the app instead of restoring it keeps a „Megszakadt visszaállítás" card about an app that no longer exists. Measured shape, not hypothetical: on 9201 the notice cleared only when homebox was restored again (08:54:50Z, card count 0) — the teardown deliberately took that path before removing it. **Fix shape:** `removeStack` clears the app's notice (a `ClearInterruptedRestore(stack)` beside the existing update-hold clear, R-491's precedent), with a wiring test. Not fixed in v0.246.0: found after the release was built; one release per repo per session. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-554** | **[P3-LOW] Delete the first-boot setup wizard — obsolete by design, still reachable.** OPERATOR DECISION 2026-09-17 (localisation starter, decision 4: „out of scope, obsolete"). `02-controller-module-map.md` L56 calls `internal/setup/` obsolete; `cmd/controller/main.go` L322 still enters it when `setup.NeedsSetup(cfg)` — `customer.id` empty after bootstrap ingestion, or a `.needs-setup` marker (`internal/setup/setup.go` L17-25). Ingestion leaves `customer.id` empty on a missing/invalid `bootstrap.json`, a failed hub pull, or a failed merge/write/reload (`internal/bootstrap/bootstrap.go` L109-163) — so a box whose first boot cannot reach the hub shows a household an 8-page wizard (95 Hungarian strings, its own template set and CSRF). **Fix shape:** decide what such a box shows instead (a single „cannot reach Felhom yet, retrying" page — needs no decision beyond copy), then delete `internal/setup/` and `runSetupMode`; red-proof that a failed ingestion renders the waiting page, not a 404. **Check first** whether any drill/golden path still relies on `.needs-setup`. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-555** | **[P3-LOW] The wire-contract gate counts a field as received when its name appears in a Go COMMENT on the receiving side.** FOUND 2026-09-17 by CC adding the report's `language` field (controller v0.247.0): `scripts/wire_contract_gate.py` passed WITHOUT an allowlist entry, because `receiver_tokens()` tokenises whole files and the word „language" occurs hub-side only in a comment (`hub/internal/web/configs.go:558`, „this page's existing language"). The shape is the gate's own named failure class (name-for-fact, R-421): any English tag name that also appears in hub prose passes unread. The field was allowlisted by hand with this row named. **Fix shape:** strip `//` and `/* */` comments (and template `{{/* */}}`) before tokenising; add the decoy „a tag whose name appears only in a receiver comment must convict"; expect a handful of currently-passing tags to surface — each is a finding, not noise. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-557** | **[P3-LOW] Localisation slice 2 — Go-side customer strings follow the language.** PLAN 2026-09-17 (10 §10). Inventory §2.2: 947 shown + 184 error literals in 84 files; 237 format strings, 111 concatenations, 42 numeric `%d` (English plurals). Flash messages travel inside the redirect URL (`?flash=<Hungarian>`) and must become keys; `cloudflare/countries.go` (113 country names); alert texts; handler errors printed with `err.Error()`. **After R-553** (the four compare-not-show sites). Cost 16–20 CC-hours. **DEPENDENCY added 2026-09-17 (R-553 shipped, v0.251.0):** the behaviour-by-wording sites are fixed, so this slice is unblocked — EXCEPT one producer: `"Sikeres — nincs mentésre jelölt alkalmazás"` (controller/internal/backup/offbox.go) must stay Hungarian until **R-570** closes, because the page's legacy fallback still reads it on boxes that have not run off-site since 0.251.0. Everything else this slice touches is now decided by a kind, not by its words. **RELEASE A SHIPPED 2026-09-18 (controller v0.252.0):** 226 Go literals converted -- flash-as-key (8 writers, 8 readers, legacy prose still shown verbatim), page data and view-model text, the `internal/api` JSON answers, the alert banners (`Alert.MessageKey`), 237 country names at DISPLAY (the cloudflare table is untouched -- only CODES are on the wire, the task claimed otherwise), and the four app-named page titles (R-566 CLOSED). New gate `i18n_go_parity.py` + `i18n_go_base.json` (7 467 base-commit literals, frozen) refuses any key whose Hungarian is not byte-identical; three decoys. Wire goldens freeze `internal/monitor` and `internal/notify` -- the hub MAILS the event message when it has no `customerMessages` entry, so those stay Hungarian until R-558. `HU_FORMAL_CEILING` 16 -> 18 (measured, no word changed). **RELEASE B (next): 176 `fmt.Errorf`/`errors.New` literals** carry a key via `util.MsgError`. **RELEASE C: persisted text**, written in the box language at write time (the task's s16 option 1, its stated default). Gaps found and filed: R-572, R-573, R-574. | **IN PROGRESS - release A shipped 2026-09-18; rank P3-LOW; owner: CC** |
|
||||
| **R-557** | **[P3-LOW] Localisation slice 2 — Go-side customer strings follow the language.** PLAN 2026-09-17 (10 §10). Inventory §2.2: 947 shown + 184 error literals in 84 files; 237 format strings, 111 concatenations, 42 numeric `%d` (English plurals). Flash messages travel inside the redirect URL (`?flash=<Hungarian>`) and must become keys; `cloudflare/countries.go` (113 country names); alert texts; handler errors printed with `err.Error()`. **After R-553** (the four compare-not-show sites). Cost 16–20 CC-hours. **DEPENDENCY added 2026-09-17 (R-553 shipped, v0.251.0):** the behaviour-by-wording sites are fixed, so this slice is unblocked — EXCEPT one producer: `"Sikeres — nincs mentésre jelölt alkalmazás"` (controller/internal/backup/offbox.go) must stay Hungarian until **R-570** closes, because the page's legacy fallback still reads it on boxes that have not run off-site since 0.251.0. Everything else this slice touches is now decided by a kind, not by its words. **RELEASE A SHIPPED 2026-09-18 (controller v0.252.0):** 226 Go literals converted -- flash-as-key (8 writers, 8 readers, legacy prose still shown verbatim), page data and view-model text, the `internal/api` JSON answers, the alert banners (`Alert.MessageKey`), 237 country names at DISPLAY (the cloudflare table is untouched -- only CODES are on the wire, the task claimed otherwise), and the four app-named page titles (R-566 CLOSED). New gate `i18n_go_parity.py` + `i18n_go_base.json` (7 467 base-commit literals, frozen) refuses any key whose Hungarian is not byte-identical; three decoys. Wire goldens freeze `internal/monitor` and `internal/notify` -- the hub MAILS the event message when it has no `customerMessages` entry, so those stay Hungarian until R-558. `HU_FORMAL_CEILING` 16 -> 18 (measured, no word changed). **RELEASE B SHIPPED 2026-09-18 (controller v0.253.0): all 179 error literals carry a key** via `util.MsgError` -- `Error()` is still the Hungarian byte for byte, `errors.Is` answers for the kind AND a wrapped cause, an error ARGUMENT renders recursively, and a foreign error (restic/docker/ssh/stdlib) prints verbatim. 76 display sites go through `errText`, pinned by `TestNoErrErrorInPageOutput`. `memoryVerdict` returns an error; `UpdateRefusal` gained a `Cause`. Plurals are a BUNDLE rule (a key with `.one`/`.other` takes its count first), not a call-site flag. **ZERO Hungarian error literals remain** (ASCII search with both controls, case-insensitive). Two tooling defects found and filed: R-576 (the converter dropped concatenations and the gate could not see it) and the case-sensitive counter. New gap: R-575. **RELEASE C: persisted text**, written in the box language at write time (the task's s16 option 1, its stated default). Gaps found and filed: R-572, R-573, R-574. | **IN PROGRESS - releases A and B shipped 2026-09-18; release C (persisted text) remains; rank P3-LOW; owner: CC** |
|
||||
| **R-558** | **[P3-LOW] Localisation slice 3 — the hub's customer e-mails follow the household's language; the operator sets it at customer creation.** PLAN 2026-09-17 (10 §3, §10), operator decision 2. The box already reports `"language"` (controller v0.247.0); nothing hub-side reads it. Build: a per-customer language on the hub (default hu) set at creation and rendered into `controller.yaml` beside `customer.*` (`hub/internal/configgen/configgen.go`), used by the box only while `settings.json` has no choice; the dispatcher picks the language the box REPORTS; English for the 39 `customerMessages`, the 4 severity labels, the body wrapper, the claim/re-enroll/reset/claimed/self-bind mails and the public bind page (inventory §2.3). Delete the `language` allowlist entry in `wire_contract_gate.py` when the field is read. Cost 6-8 CC-hours, one hub + one controller release. **THE WIRE SURVEY (R-557 release A, 2026-09-18) HANDS THIS ROW ITS INPUT LIST:** (1) `internal/notify/notifier.go` -- 31 event messages. The task assumed the hub composes its own mail from the event kind; it does NOT. `FormatCustomerEmail` uses `customerMessages[eventType]` when it has one and **falls back to the controller's message when it has none**, and appends it as „- Üzenet: %s” whenever the two differ -- several types carry no entry precisely so the controller's dynamic sentence IS the mail (hub `api/handler.go` L2034/L2045/L2066). So an English household's mail is Hungarian until this row ships. (2) `internal/monitor/healthcheck.go` -- 5 storage sentences in report `health.warnings`/`health.issues`, operator-facing. Both are frozen by wire goldens in those packages, so no later slice can translate them by accident; this row is what unfreezes them. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-559** | **[P3-LOW] Localisation slice 4 — the console banner and the download page in English.** IN SCOPE — operator ruling 2026-09-17 evening („yes", 10 §11 1b). PLAN 2026-09-17 (10 §10, open decision 1b): the starter listed them; the operator's scope ruling names „the controller, emails, guide, app catalog" and not them. Inventory §2.4: 34 banner lines (`scripts/iso/felhom-bootstrap.sh`, printf to the console; `/etc/issue` byte-coupled to `iso/pkg/debian/postinst`, console font avoids ő/ű) and 32 strings on `website/letoltes.html`. Cost 4–6 CC-hours plus an ISO release train. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-560** | **[P3-LOW] Localisation slice 5 — catalog cards, settings and first steps in English.** PLAN 2026-09-17 (10 §7, §10). Inventory §2.5: 835 strings, ~4 984 words across all 53 `.felhom.yml` (use_cases 262, first_steps 233, deploy_fields descriptions 79 / labels 68, prerequisites 60, description 56, tagline 53). Proposed format: an `i18n: {en: {...}}` block inside each `.felhom.yml` (controller's `yaml.Unmarshal` ignores unknown keys, so older controllers are unaffected), field-by-field fallback. Needs a controller change to read it and catalog copy gates with an English rule. Cost 12–16 CC-hours. | **READY - rank P3-LOW; owner: CC (catalog + controller)** |
|
||||
@@ -750,6 +750,8 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-572** | **[P3-LOW] Two copy-producing template helpers have no English form, so an English page renders „vasárnap" and „%d órája" in Hungarian.** FOUND 2026-09-18 by localisation slice 2 release A (R-557, controller v0.252.0): `web/i18n_web.go` `localeFuncs` overrides `stateLabel`, `timeAgo`, `timeAgoStr`, `nextRunLabel`, `statusText` and `infraMeta` from the bundle, but NOT `pruneLabel`/`nextPruneLabel` (weekday names — „vasárnap", `funcmap.go` L366/L370) or `fmtDuration`. A template calling either renders Hungarian on an English page, and `TestI18nEnglishPages` does not see „vasárnap" because it subtracts fixture DATA strings — this is the R-565 shape one layer over. **Fix shape:** add both to `localeFuncs` with `func.prune.*` / `func.duration.*` keys, extend `TestLocaleFuncsHungarianBundleMatchesFuncMap` to cover them, and add a render case that exercises a weekly prune schedule in English. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-573** | **[P3-LOW] The agent-channel and endpoint-drift banners reach the dashboard as finished Hungarian, so they stay Hungarian on an English page.** FOUND 2026-09-18 by localisation slice 2 release A (R-557): `Alert` now carries `MessageKey`+`MessageArgs` and `GetAlerts(lang)` renders them, but `SetAgentChannelAlert(down, msg)` and `SetEndpointDriftAlert(drift, msg)` take a composed SENTENCE from the channel-health checker (`internal/channelhealth`), so those two banners set `Message` directly and render verbatim in both languages. They are the two most operationally important banners there are (R-77, the 17.5 h 2026-07-25 outage). **Fix shape:** the checker passes a KIND plus its parameters instead of a sentence — the same shape R-553 used for the off-site warning — and the two setters build `MessageKey`/`MessageArgs`. One render case per banner in English. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-574** | **[P3-LOW] `web/handler_debug.go` mixes page copy with JSON payload, so neither half could be converted safely.** FOUND 2026-09-18 by localisation slice 2 release A (R-557): the file holds 39 Hungarian literals and the inventory classifies them by STATEMENT, not by data flow (`I18N-INVENTORY-2026-09-17.md` §4), so which are section headings the debug page renders and which are values inside a diagnostic dump the operator copies out is not established. Converting a dump value would change what an operator pastes into a report; leaving a heading Hungarian leaves a half-English page. **Fix shape:** walk the file once and label every literal page-copy or payload in the same table slice 2 release A used, then convert only the page-copy half. Belongs to slice 2 release B or C. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **R-575** | **[P3-LOW] The soft memory-overcommit warning is returned as a Hungarian STRING, so it renders Hungarian on an English page.** FOUND 2026-09-18 by localisation slice 2 release B (R-557, controller v0.253.0): `memoryVerdict` now returns its REFUSAL as an error carrying a key (`util.MsgErrorf(ErrNotEnoughMemory, …)`), but its WARNING is a plain string with no error to carry one, and no language is known where it is built — so it goes through `msgHU` and is always Hungarian. The copy is in the bundle (a translator sees it; `i18n_go_parity.py` pins it), only the render is fixed to `hu`. The named helper and this consequence are stated in `internal/stacks/deploy_errors.go`. **Fix shape:** the caller carries the key the way `Alert` and `UpdateRefusal` now do — `memoryVerdict` returns `(refusal error, warningKey string, warningArgs []any)` and the deploy answer renders it — not a helper guessing a language it cannot know. | **READY - rank P3-LOW; owner: CC** |
|
||||
| **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-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