v0.308.0 (R-554, held-back-able): recovery-info.txt points at the dashboard's Restore page and the recovery code; the first-boot setup wizard is deleted

Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before
release. A box whose first ingestion failed now serves one bilingual waiting page and retries every
minute instead of the wizard. Own commit so the lead can hold it back.

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-12 15:38:28 +02:00
parent fd28d70bff
commit faef8f8a91
36 changed files with 421 additions and 1916 deletions
+2 -3
View File
@@ -3,7 +3,7 @@
grep-gate zero was a false negative). This scans templates by Unicode codepoint.
Run from controller/: python scripts/emoji_gate.py
Exit 1 if any emoji/pictograph remains in web+setup templates.
Exit 1 if any emoji/pictograph remains in the web templates.
"""
import io, os, sys, unicodedata
@@ -12,7 +12,6 @@ import i18n_bundle # noqa: E402
ROOTS = [
os.path.join("internal", "web", "templates"),
os.path.join("internal", "setup", "templates"),
]
# Codepoint ranges that count as "emoji / pictographs / dingbats" for UI-copy purposes.
@@ -88,7 +87,7 @@ def main():
if total:
print("EMOJI GATE FAILED: %d emoji found" % total)
sys.exit(1)
print("emoji gate OK — no emoji in web/setup templates")
print("emoji gate OK — no emoji in web templates")
if __name__ == "__main__":
+3 -3
View File
@@ -1,6 +1,9 @@
{
"_comment": "Localisation slice 2 (R-557). key -> the base-commit Go literal it replaced, or the ORDERED list of literals a concatenation joined. Checked by scripts/i18n_go_parity.py against scripts/i18n_go_base.json, which is frozen at 736f54b49610 (the base commit of release v0.252.0). A key whose Hungarian text is not byte-identical to what the Go code said fails the gate.",
"_preexisting": {
"waiting.title": "BORN AS A KEY (R-554, 2026-10-12) -- the title of the page an unconfigured box shows while it retries its configuration, replacing the deleted setup wizard. Pinned by internal/bootstrap TestWaitForConfig_ServesWaitingPageThenStarts.",
"waiting.body": "BORN AS A KEY (R-554, 2026-10-12) -- the body of that waiting page. Pinned by internal/bootstrap TestWaitForConfig_ServesWaitingPageThenStarts.",
"recovery_info.body": "BORN AS A KEY (R-554, 2026-10-12, `09` §3 decision 222) -- the NEW text of recovery-info.txt, pointing at the dashboard's Restore page and the recovery code instead of the deleted setup wizard. The operator approves the Hungarian before release. Pinned by internal/recovery TestRecoveryInfo_PointsAtDashboardNotWizard.",
"err.backup.db_restore_failed_held_mixed": "BORN AS A KEY (R-893, 2026-10-08) -- a NEW sentence for a failed off-site replay whose database rollback worked but whose files/volumes/version had already moved; the app is held. Never a Go literal; pinned by internal/backup/r893_mixed_restore_test.go.",
"err.backup.restore_failed_held_mixed": "BORN AS A KEY (R-893, 2026-10-08, security review) -- a restore that stopped half-way (placement, volume replay or DB start failed) after files/volumes/version had moved; the app is HELD. Pinned by internal/backup/r893_mixed_restore_test.go.",
"note.reconstitute.held_mixed": "BORN AS A KEY (R-893, 2026-10-08) -- the hold sentence for that held app (HoldReasonRestoreMixed). Never a Go literal; pinned by internal/backup/r893_mixed_restore_test.go.",
@@ -170,7 +173,6 @@
"flash.backup.window_invalid_time": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"api.deploy.netstorage_unreachable": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"api.deploy.no_space": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"debug.api.reset_confirm_invalid": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"err.web.formazas_kesz_de_az_uj_fajlrendszer": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"err.selfupdate.mentes_fut_probalja_kesobb": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
"err.stacks.field_required_password": "R-516 (2026-10-06) -- REWORDED on purpose: the formal („ön\") verb forms in this sentence became the product's te-form; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_WidenedFormalFormsAreGone.",
@@ -676,8 +678,6 @@
"debug.api.gitea_reachable": "Gitea elérhető (HTTP %d, %dms)",
"debug.api.telemetry_collected": "Telemetria összegyűjtve: %d app, %d hiba, %d figyelmeztetés (%dms)",
"debug.api.selfupdate_unconfigured": "Self-update nincs konfigurálva",
"debug.api.marker_write_failed": "Marker fájl írási hiba: %v",
"debug.api.restarting_to_setup": "Controller újraindítása setup módba...",
"debug.api.agent_unconfigured": "Az ügynök nincs konfigurálva ezen a rendszeren.",
"debug.api.agent_logs_unsupported": "Az ügynök naplónézete az ügynök következő frissítése után érhető el.",
"debug.api.bundles_found": "%d csomag található",
+2 -1
View File
@@ -63,7 +63,8 @@ BASE_FILE = os.path.join(HERE, "i18n_go_base.json")
KEYS_FILE = os.path.join(HERE, "i18n_go_keys.json")
# Directories whose Go literals are IN SCOPE for the capture. cmd/ and internal/ minus the
# first-boot wizard, which is out of scope and slated for deletion (R-554, 10-localisation.md s6).
# first-boot wizard, which was out of scope and is DELETED in v0.308.0 (R-554). The skip stays: the frozen
# base capture was taken with it, so a rescan must skip the same path to measure the same thing.
SCOPE_ROOTS = ["internal", "cmd"]
SCOPE_SKIP = [os.path.join("internal", "setup")]
+1 -2
View File
@@ -7,7 +7,7 @@ to the signature characters below, so any hit is a defect. The allowlist is ZERO
design — fix the text, don't allowlist it. (Windows grep false-negatives multibyte —
this gate is Python by the multibyte rule, like emoji_gate.)
Scans every template (web + setup) and every Go source file:
Scans every template (web) and every Go source file:
- the file must decode as strict UTF-8 (undecodable bytes = broken already), and
- the decoded text must contain none of the double-encoding signature characters:
à (U+00C3)  (U+00C2) Ă (U+0102) ă (U+0103) ˘ (U+02D8) ˇ (U+02C7)
@@ -19,7 +19,6 @@ import io, os, sys
ROOTS = [
os.path.join("internal", "web", "templates"),
os.path.join("internal", "setup", "templates"),
"internal", # every .go string literal / comment
"cmd",
]
@@ -16,7 +16,6 @@ import i18n_bundle # noqa: E402
ROOTS = [
os.path.join("internal", "web", "templates"),
os.path.join("internal", "setup", "templates"),
]
# A bare confirm(/prompt(/alert( CALL with an argument: not preceded by an identifier character,
+1 -2
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""D1 §10 JS element-ID integrity gate.
For every template under internal/web/templates and internal/setup/templates, extract each
For every template under internal/web/templates (the setup wizard templates are deleted, R-554), extract each
getElementById('X') / querySelector('#X') literal used by the file's inline JS and assert an
id="X" exists in the SAME file — or that the ID is created by that file's own JS (innerHTML /
insertAdjacentHTML string containing id="X" / id='X'), or is explicitly allowlisted below with
@@ -14,7 +14,6 @@ import io, os, re, sys
ROOTS = [
os.path.join("internal", "web", "templates"),
os.path.join("internal", "setup", "templates"),
]
# Dynamic-ID exceptions: (template, id-prefix-or-name) -> justification.