v0.297.0: burn-down round 2 — 24 small rows (R-591 R-568 R-567 R-363 R-547 R-10 R-552 R-251 R-104 R-619 R-362 R-675 R-256 R-257 R-240 R-365 R-425 R-565 R-564 R-603 R-454 R-208 R-457-swept) + the banner countdown and deepCopyStack twins; MinAgent 0.131.0
gates / gates (push) Failing after 50s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 20:12:56 +02:00
parent 114ff2761a
commit 1453cfc69b
68 changed files with 3953 additions and 116 deletions
+13 -3
View File
@@ -385,6 +385,7 @@
"backups_remote.a_tavoli_tarhelyen_levo_mentesek": "The backups on the remote storage were made with an earlier key that is no longer available (usually after a reinstall). Because of that, no new backup can be written to the store right now. The existing backups are not damaged. This machine cannot tell whether they can be opened later — that depends on whether their key still exists. If you need them, write to us.",
"backups_remote.a_tavoli_tarolo_masik_kulccsal": "The remote store holds backups made with a different key",
"backups_remote.a_teljes_mentes_tullepne_a": "The full backup would go over the storage quota — only the configuration and the database are backed up.",
"backups_remote.abandon_overdue": "As you asked, your earlier remote backups have been due for deletion since <strong>{{.AbandonDate}}</strong>: we will permanently delete them during the next daily maintenance.",
"backups_remote.adatbazisok_mentese_a_pillanatkephez": "Backing up databases for the snapshot…",
"backups_remote.adatok_visszaszerzese": "Retrieve data",
"backups_remote.addig_meggondolhatod_magad_ha_megvan": "Until then you can change your mind: if you have your recovery code,\n your backups can be retrieved, and the deletion is cancelled.",
@@ -394,6 +395,7 @@
"backups_remote.biztonsagi_mentes_tavoli_mentes": "Backup — Remote backup",
"backups_remote.cel_cime_ip_vagy_hosztnev": "Target address (IP or hostname — NAS or SFTP server)",
"backups_remote.ehhez_a_gephez_egy_korabbi": "This machine <strong>has an earlier recovery code</strong>, and your earlier backups still exist.\n Creating a new code <strong>would make your old backups unreachable</strong>, so it cannot start now.\n First <a href=\"/recovery\">enter your existing code</a> — or tell us there if you do not want the earlier data back.",
"backups_remote.elapsed_seconds_unit": "s",
"backups_remote.fajl": "files",
"backups_remote.feldolgozas_alatt": "· now processing:",
"backups_remote.felhasznalo": "User",
@@ -1164,6 +1166,7 @@
"disk.err.target_required": "a target storage is required for the move",
"disk.err.unknown_mode": "unknown mode (migrate or anyway)",
"disk.err.wipe_failed": "the erase failed: %s",
"err.backup.restore_drive_gone": "The drive “%s” cannot be reached, so the restore could not start. Connect the drive again, then start the restore again.",
"err.stacks.remove_keep_data_while_support": "While support is working on this app, its data cannot be deleted — you can remove the app and keep its data.",
"err.appexport.a_mentes_nem_tartalmaz_alkalmazasadatot_0": "the backup holds no app data (0 data folders, 0 volumes for an app that stores data)",
"err.appexport.a_z_adatkonyvtar_tartalma_hianyzik_a": "the contents of data folder %q are missing from the package",
@@ -1412,10 +1415,10 @@
"flash.offbox.first_setup_needs_key": "The first setup needs both the SSH private key and the target machine’s known-host line.",
"flash.offbox.full_needs_confirmation": "The full restore cannot run without your confirmation.",
"flash.offbox.full_restore_started": "The full restore has started — the status updates here.",
"flash.offbox.mgr_unavailable": "The backup manager is not reachable.",
"flash.offbox.mgr_unreachable": "The backup manager cannot be reached.",
"flash.offbox.mgr_unavailable": "Backups cannot be managed right now. Try again in a few minutes; if it still does not work, contact Felhom support.",
"flash.offbox.mgr_unreachable": "Backups cannot be managed right now. Try again in a few minutes; if it still does not work, contact Felhom support.",
"flash.offbox.needs_confirmation": "The restore needs your confirmation.",
"flash.offbox.not_orphaned": "The off-site store is not orphaned.",
"flash.offbox.not_orphaned": "There is no need to start a new remote backup: your current remote backup opens normally and keeps working. If you still see a problem with it, contact Felhom support.",
"flash.offbox.path_absolute": "The storage path must be absolute (it has to start with /).",
"flash.offbox.recover_started": "The recovery has started — the status updates here.",
"flash.offbox.repo_orphaned": "The remote store is orphaned — start a new remote backup first, the way the card shows.",
@@ -1552,6 +1555,7 @@
"layout.a_korabbi_tavoli_menteseid_megvannak": "Your earlier remote backups are safe — you need your recovery code to open them.",
"layout.a_korabbi_tavoli_menteseid_megvannak_2": "Your earlier remote backups are safe, but this device needs your recovery code for them.",
"layout.a_korabbi_tavoli_menteseidet_nap": "We will permanently delete your earlier remote backups in <strong>{{.RecoveryAbandonDays}} {{if eq .RecoveryAbandonDays 1}}day{{else}}days{{end}}</strong> ({{.RecoveryAbandonDate}}), as you asked.",
"layout.abandon_overdue": "As you asked, your earlier remote backups have been due for deletion since <strong>{{.RecoveryAbandonDate}}</strong>: we will permanently delete them during the next daily maintenance.",
"layout.a_tavoli_mentes_szunetel_amig": "Remote backup is paused until you create your recovery code.\n <a href=\"/backup/escrow\"><strong>Create recovery code &rarr;</strong></a>",
"layout.a_vezerlopult_meg_nincs_jelszoval": "The dashboard is not protected by a password yet — we will send you the setup code by e-mail soon.",
"layout.addig_meg_visszaszerezheted_oket_a": "Until then you can still get them back with your recovery code.",
@@ -1689,6 +1693,7 @@
"monitoring.utolso_sikeres_jelentes": "Last successful report",
"monitoring.uzemido": "Uptime",
"note.offsite.fail_head": "The remote backup failed",
"note.offsite.fail_locked": "The remote store is still locked by an earlier run that was cut short, and the backup could not unlock it",
"note.offsite.fail_no_repo": "There is no backup store at the remote target",
"note.offsite.fail_no_units": "There was nothing to back up: none of the selected apps has a backup",
"note.offsite.fail_orphaned": "The remote store was made with an earlier key that is no longer available",
@@ -1829,6 +1834,11 @@
"recovery.ugyhogy_tartsd_keznel": "so keep it at hand.",
"recovery.vissza_a_kezdolapra": "Back to the home page",
"recovery.visszaszerzese": "your data",
"restore.refuse.files.head": "This backup does not hold the files of the app, so we do not restore the database over them — the files stay where they are. ",
"restore.refuse.files.none": "There is no copy of these files at the moment — turn on the remote backup, or connect a second drive.",
"restore.refuse.files.offsite": "The files can be restored from the remote copy: Backup → Restore, “Full restore (files + database)”.",
"restore.refuse.files.second_drive": "The files can be restored from the copy on the second drive: “Restore files”.",
"restore.refuse.files.second_drive_whole": "The app can be brought back with its files from the copy on the second drive: “Full restore from the copy”.",
"settings.app_email_off": "App email is off.",
"settings.app_email_on": "App email is on. Turn it on for each app that should send email.",
"settings.app_email_save_error": "Something went wrong while saving the app email setting",
+13 -3
View File
@@ -381,6 +381,7 @@
"backups_remote.a_tavoli_tarhelyen_levo_mentesek": "A távoli tárhelyen lévő mentések egy korábbi, már nem elérhető kulccsal készültek (jellemzően újratelepítés után). Emiatt új mentés jelenleg nem írható a tárolóba. A meglévő mentések nem sérültek. Azt viszont ez a gép nem tudja megállapítani, hogy később megnyithatók-e — ez attól függ, megvan-e még a hozzájuk tartozó kulcs. Ha szükséged van rájuk, írj nekünk.",
"backups_remote.a_tavoli_tarolo_masik_kulccsal": "A távoli tároló másik kulccsal készült mentéseket tartalmaz",
"backups_remote.a_teljes_mentes_tullepne_a": "A teljes mentés túllépné a tárhelykeretet — csak a konfiguráció és az adatbázis kerül mentésre.",
"backups_remote.abandon_overdue": "A kérésed szerint a korábbi távoli mentéseid törlése <strong>{{.AbandonDate}}</strong> óta esedékes: a következő napi karbantartáskor véglegesen töröljük őket.",
"backups_remote.adatbazisok_mentese_a_pillanatkephez": "Adatbázisok mentése a pillanatképhez…",
"backups_remote.adatok_visszaszerzese": "Adatok visszaszerzése",
"backups_remote.addig_meggondolhatod_magad_ha_megvan": "Addig meggondolhatod magad: ha megvan a helyreállítási kódod,\n a mentéseid visszaszerezhetők, és a törlés elmarad.",
@@ -390,6 +391,7 @@
"backups_remote.biztonsagi_mentes_tavoli_mentes": "Biztonsági mentés — Távoli mentés",
"backups_remote.cel_cime_ip_vagy_hosztnev": "Cél címe (IP vagy hosztnév — NAS vagy SFTP-kiszolgáló)",
"backups_remote.ehhez_a_gephez_egy_korabbi": "Ehhez a géphez <strong>egy korábbi helyreállítási kód tartozik</strong>, és a korábbi mentéseid még megvannak.\n Új kód létrehozása <strong>a régi mentéseidet elérhetetlenné tenné</strong>, ezért most nem indítható.\n Előbb <a href=\"/recovery\">add meg a meglévő kódodat</a> — vagy ott jelezheted, ha nem kéred vissza a korábbi adatokat.",
"backups_remote.elapsed_seconds_unit": "mp",
"backups_remote.fajl": "fájl",
"backups_remote.feldolgozas_alatt": "· feldolgozás alatt:",
"backups_remote.felhasznalo": "Felhasználó",
@@ -1159,6 +1161,7 @@
"disk.err.target_required": "céltároló kötelező az áthelyezéshez",
"disk.err.unknown_mode": "ismeretlen mód (migrate vagy anyway)",
"disk.err.wipe_failed": "törlés sikertelen: %s",
"err.backup.restore_drive_gone": "A(z) „%s” meghajtó nem érhető el, ezért a visszaállítás nem tudott elindulni. Csatlakoztasd újra a meghajtót, majd indítsd el újra a visszaállítást.",
"err.stacks.remove_keep_data_while_support": "Amíg az ügyfélszolgálat foglalkozik az alkalmazással, az adatai nem törölhetők — az alkalmazást az adatai megtartásával eltávolíthatod.",
"err.appexport.a_mentes_nem_tartalmaz_alkalmazasadatot_0": "a mentés nem tartalmaz alkalmazásadatot (0 adatkönyvtár, 0 kötet egy adattárolós alkalmazásnál)",
"err.appexport.a_z_adatkonyvtar_tartalma_hianyzik_a": "a(z) %q adatkönyvtár tartalma hiányzik a csomagból",
@@ -1403,10 +1406,10 @@
"flash.offbox.first_setup_needs_key": "Az első beállításhoz az SSH privát kulcs és a célgép ismert-host sora is kötelező.",
"flash.offbox.full_needs_confirmation": "A teljes visszaállítás megerősítés nélkül nem hajtható végre.",
"flash.offbox.full_restore_started": "A teljes visszaállítás elindult — az állapot itt frissül.",
"flash.offbox.mgr_unavailable": "A mentéskezelő nem elérhető.",
"flash.offbox.mgr_unreachable": "A mentéskezelő nem érhető el.",
"flash.offbox.mgr_unavailable": "A mentések kezelése most nem érhető el. Próbáld újra néhány perc múlva; ha akkor sem megy, keresd a Felhom ügyfélszolgálatát.",
"flash.offbox.mgr_unreachable": "A mentések kezelése most nem érhető el. Próbáld újra néhány perc múlva; ha akkor sem megy, keresd a Felhom ügyfélszolgálatát.",
"flash.offbox.needs_confirmation": "A visszaállításhoz megerősítés szükséges.",
"flash.offbox.not_orphaned": "Az offsite tároló nincs elárvult állapotban.",
"flash.offbox.not_orphaned": "Új távoli mentést nem kell indítani: a mostani távoli mentésed rendben megnyitható, és tovább működik. Ha mégis gondot látsz vele, keresd a Felhom ügyfélszolgálatát.",
"flash.offbox.path_absolute": "A tárhely útvonalának abszolútnak kell lennie (/-rel kezdődjön).",
"flash.offbox.recover_started": "A helyreállítás elindult — az állapot itt frissül.",
"flash.offbox.repo_orphaned": "A távoli tároló elárvult — előbb indíts új távoli mentést a kártyán látható módon.",
@@ -1540,6 +1543,7 @@
"layout.a_korabbi_tavoli_menteseid_megvannak": "A korábbi távoli mentéseid megvannak — a megnyitásukhoz a helyreállítási kódod szükséges.",
"layout.a_korabbi_tavoli_menteseid_megvannak_2": "A korábbi távoli mentéseid megvannak, de ehhez a géphez a helyreállítási kódod szükséges.",
"layout.a_korabbi_tavoli_menteseidet_nap": "A korábbi távoli mentéseidet <strong>{{.RecoveryAbandonDays}} nap múlva</strong> ({{.RecoveryAbandonDate}}) véglegesen töröljük, a kérésed szerint.",
"layout.abandon_overdue": "A korábbi távoli mentéseid törlése a kérésed szerint <strong>{{.RecoveryAbandonDate}}</strong> óta esedékes: a következő napi karbantartáskor véglegesen töröljük őket.",
"layout.a_tavoli_mentes_szunetel_amig": "A távoli mentés szünetel, amíg nem hozod létre a helyreállítási kódot.\n <a href=\"/backup/escrow\"><strong>Helyreállítási kód létrehozása &rarr;</strong></a>",
"layout.a_vezerlopult_meg_nincs_jelszoval": "A vezérlőpult még nincs jelszóval védve — a beállító kódot hamarosan e-mailben küldjük.",
"layout.addig_meg_visszaszerezheted_oket_a": "Addig még visszaszerezheted őket a helyreállítási kóddal.",
@@ -1677,6 +1681,7 @@
"monitoring.utolso_sikeres_jelentes": "Utolsó sikeres jelentés",
"monitoring.uzemido": "Üzemidő",
"note.offsite.fail_head": "A távoli mentés nem sikerült",
"note.offsite.fail_locked": "A távoli tárhelyet egy korábbi, félbeszakadt futás zárva hagyta, és a mentés nem tudta feloldani",
"note.offsite.fail_no_repo": "A távoli tárhelyen nincs mentési adattár",
"note.offsite.fail_no_units": "Nem volt mit menteni: egyetlen kijelölt alkalmazásnak sem található mentése",
"note.offsite.fail_orphaned": "A távoli tárhely egy korábbi, már nem elérhető kulccsal készült",
@@ -1817,6 +1822,11 @@
"recovery.ugyhogy_tartsd_keznel": "úgyhogy tartsd kéznél.",
"recovery.vissza_a_kezdolapra": "Vissza a kezdőlapra",
"recovery.visszaszerzese": "visszaszerzése",
"restore.refuse.files.head": "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. ",
"restore.refuse.files.none": "Ezekről a fájlokról jelenleg nincs másolat — kapcsold be a távoli mentést, vagy csatlakoztass egy második meghajtót.",
"restore.refuse.files.offsite": "A fájlok a távoli másolatból állíthatók vissza: Biztonsági mentés → Visszaállítás, „Teljes visszaállítás (fájlok + adatbázis)”.",
"restore.refuse.files.second_drive": "A fájlok a második meghajtó másolatából állíthatók vissza: „Fájlok visszaállítása”.",
"restore.refuse.files.second_drive_whole": "Az alkalmazás a fájljaival együtt a második meghajtó másolatából állítható vissza: „Teljes visszaállítás a másolatból”.",
"settings.app_email_off": "Alkalmazás-email kikapcsolva.",
"settings.app_email_on": "Alkalmazás-email bekapcsolva. Kapcsold be az egyes alkalmazásoknál is, ahol email-küldést szeretnél.",
"settings.app_email_save_error": "Hiba az alkalmazás-email beállítás mentésekor",
@@ -0,0 +1,143 @@
package i18n
import (
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"testing"
)
// R-603: html/template escapes ' " & < > in DATA. A bundle value that Go code renders into a page as
// data (a flash, a note, an error) therefore never appears verbatim on the page: "The system backup's
// drive…" arrives as "backup&#39;s", and a strings.Contains assertion for it fails exactly like a
// missing sentence — which sends the next person to re-fix a handler that was never broken. (Template
// copy is expanded textually by Expand and is NOT escaped; only Go-named values travel as data.)
//
// THE GATE: a Go-named value may not carry an HTML-escapable character unless it is registered below.
// The registered set is the measured state on 2026-10-05; a NEW value fails here with this pointer,
// so whoever writes it either rewords it or registers it AND asserts it with html.EscapeString(want).
var r603Registered = map[string]bool{
"alert.endpoint_drift": true, "kept.choice.title": true, "kept.choice.fresh.desc": true,
"err.backup.unit_version_mismatch": true, "note.tier2.unit_preserved": true,
"err.stacks.setup_gate_failed": true, "err.kept.occupied": true, "update.refusal.no_backup": true,
"update.error.journal_failed": true, "note.unit_restore_settings_only": true,
"note.tier2_no_coverage": true, "note.tier2_unit_available": true, "note.tier2_unit_not_covered": true,
"note.tier2_unit_confirm_base": true, "note.tier2_unit_stale_clause": true,
"note.tier2_unit_stale_notice_fmt": true, "note.restore.whole_files": true,
"note.restore.scratch_state": true,
}
var r603Escapable = regexp.MustCompile(`['"&<>]`)
var r603KeyShape = regexp.MustCompile(`^[a-z][a-z0-9_]*(?:\.[a-z0-9_\-]+)+$`)
// goNamedKeys returns every bundle key that appears as a string literal in non-test Go source under
// the given roots — the same "named in Go" rule scripts/i18n_go_parity.py applies.
func goNamedKeys(t *testing.T, bundle map[string]string, roots ...string) map[string]string {
t.Helper()
out := map[string]string{}
fset := token.NewFileSet()
for _, root := range roots {
err := filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() || !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") {
return err
}
f, perr := parser.ParseFile(fset, p, nil, 0)
if perr != nil {
return perr
}
ast.Inspect(f, func(n ast.Node) bool {
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING {
if v, uerr := strconv.Unquote(lit.Value); uerr == nil && r603KeyShape.MatchString(v) {
if _, ok := bundle[v]; ok {
out[v] = p
}
}
}
return true
})
return nil
})
if err != nil {
t.Fatal(err)
}
}
return out
}
func r603Load(t *testing.T, lang string) map[string]string {
t.Helper()
raw, err := os.ReadFile(filepath.Join("locales", lang+".json"))
if err != nil {
t.Fatal(err)
}
var generic map[string]interface{}
if err := json.Unmarshal(raw, &generic); err != nil {
t.Fatal(err)
}
out := map[string]string{}
for k, v := range generic {
if s, ok := v.(string); ok {
out[k] = s
}
}
return out
}
// r603Offenders is the gate's verdict for one bundle: Go-named values carrying an escapable character
// that are not registered.
func r603Offenders(bundle, named map[string]string) []string {
var bad []string
for k := range named {
if r603Escapable.MatchString(bundle[k]) && !r603Registered[k] {
bad = append(bad, k)
}
}
sort.Strings(bad)
return bad
}
func TestR603_GoNamedValuesDoNotHideBehindHTMLEscaping(t *testing.T) {
hu, en := r603Load(t, "hu"), r603Load(t, "en")
named := goNamedKeys(t, hu, filepath.Join("..", "..", "internal"), filepath.Join("..", "..", "cmd"))
if len(named) < 100 {
t.Fatalf("control: only %d Go-named keys found — the source walk is not reaching the code", len(named))
}
// Decoy: a planted apostrophe in a Go-named value that is not registered must be caught.
var some string
for k := range named {
if !r603Registered[k] {
some = k
break
}
}
planted := map[string]string{}
for k, v := range en {
planted[k] = v
}
planted[some] = "The drive's copy"
if got := r603Offenders(planted, named); len(got) != 1 || got[0] != some {
t.Fatalf("control: a planted apostrophe in %s was not caught (got %v)", some, got)
}
for lang, b := range map[string]map[string]string{"hu": hu, "en": en} {
for _, k := range r603Offenders(b, named) {
t.Errorf("R-603 [%s] %s = %q (named in %s) carries a character html/template escapes in data "+
"(' \" & < >): on the page it is not these bytes, and a strings.Contains assertion for it fails "+
"like a missing sentence. Reword it (a typographic ’ is not escaped), or register it in "+
"r603Registered and assert it with html.EscapeString(want).", lang, k, b[k], named[k])
}
}
// A registration that no longer needs to be there is reported, so the list only shrinks.
for k := range r603Registered {
if _, ok := named[k]; !ok || !r603Escapable.MatchString(en[k]+hu[k]) {
t.Errorf("R-603: %s is registered but no longer a Go-named value with an escapable character — remove it", k)
}
}
}