REPORT + CONTEXT for v0.260.0 (R-524)
gates / gates (push) Successful in 27s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-21 12:59:26 +02:00
parent 8f8a64cad7
commit bdeb1c0d8e
2 changed files with 81 additions and 54 deletions
+3 -1
View File
@@ -7,7 +7,9 @@
> >
> Ask Claude Code: "Please update CONTEXT.md with what we did today" > Ask Claude Code: "Please update CONTEXT.md with what we did today"
Last updated: 2026-09-21 (v0.259.0 — the one screen the English walk stopped on, and the Backup page's promises) Last updated: 2026-09-21 (v0.260.0 — a box ahead of the catalog, and a pin that never moves backwards)
> **2026-09-21 — v0.260.0 (R-524, update arc).** A box that updated before the catalog was reverted under it read „Frissítés elérhető" over an Update that would have moved its pin BACKWARDS (measured BIGNIGHT Phase 6, privatebin 2.0.6 vs catalog 2.0.5). **The comparison gains a fourth verdict and MOVES OUT OF `web`:** `stacks.CatalogOrder` — Unknown/Current/Behind/**Ahead** — is read by BOTH the badge and `UpdatePreflight`, because a comparison implemented twice drifts. Ahead reads „Naprakész"/"Up to date" (`tag-ok`, same word and class as level — nothing for the household to do) with a title saying why; the Update is refused `downgrade` 409. **The API now renders update refusals through `errText`** — otherwise the new key would be a seam built and never wired. **Ahead is NARROW:** every differing service must be orderable AND newer, else Behind — this gate can BLOCK an update, so it errs towards letting one run. **THE TRAP, and the fixture caught it, not the design:** the first tag rule accepted only bare `X.Y.Z`, so every REAL catalog tag was unorderable and the refusal test failed with `got nil`. The rule now takes the version at the FRONT and requires the trailing suffix to be IDENTICAL on both sides — `31.0.14-apache → 31.0.15-apache` orders; `26.05.2-ls310 → -ls311`, `postgres:16-alpine`, `apache-2.57.0`, a date stamp and a digest pin do not. Ordering is `util.Version.Compare` and nothing else (one comparator, house rule). Three red-proofs, each seen to fail. **R-589 was NOT open** — it shipped in v0.258.0 and only its row was stale; a reviewer who reads ONE producer cannot see a SECOND that overrides it. **Floor NOT raised — the operator's step**; still 0.257.0. Deployed on demo-felhom 9201, demo-hp 9201 and demo-hp 9202 (scratch, upgraded from 0.245.0 for the live proof). Seven questions for Slices 6 and 7 are in `09` §3b; the state of the whole arc with drift numbers is `audits/UPDATE-ARC-STATE-2026-09-21.md`.
> **2026-09-21 — v0.259.0 (R-596 P1 + R-598, the drill's blockers).** The claim page's answers and the Backup page's protection warnings follow the reader's language. **Fourteen** live sites carrying **nine** messages, not the sixteen literals R-596 counted — and the sixteenth, `data["Title"]`, was **DEAD** (`claim.html` is standalone; `.Title` belongs to `layout.html`) and was DELETED rather than translated. `backup_handlers.go` had **nine** code literals, not twelve; three were inside comments. `degradedMessageFor` now returns a **KEY**, so the decision stays language-free in one place while the words are chosen by whoever knows the reader; `buildTierViews`/`backupTargetLabel`/`loadGuestBackup` take `lang` the `buildDataPathCards` way. **The anonymous cookie-less claim page's language chain** (`langFor` → `settings.GetLanguage` → `configLanguage` ← `cfg.Customer.Language`) **was an unpinned assumption and is now a test.** Six existing copy-contract tests were kept rather than weakened — each resolves its key through the real bundle, so they still convict on a reworded Hungarian sentence. **Two things the tests caught and review did not:** an apostrophe in an English value is escaped to `&#39;` and NEVER matches on the page (the failure reads exactly like an unwired handler — R-603), and the i18n gate convicted two of my English sentences for saying "please". **Proven live on guest 9201 through the `felhom_lang` cookie** — and the lockout proved itself unasked: Hungarian attempts locked out the English request from the same source, so the counter is per SOURCE, not per language. **The instrument trap that nearly cost a second fix (R-602): the cookie works only on ANONYMOUS pages** — `langFor` step 2 means a signed-in request reads the household's setting and ignores the cookie, so `/backups?felhom_lang=en` returns HUNGARIAN and reads like an unfixed defect. Use `?lang=` behind auth. **Floor NOT raised — the operator's step**; it still stands at 0.257.0. Guest 9201 is on **felhom-pve**, and **demo-hp answers on no route** (R-601). > **2026-09-21 — v0.259.0 (R-596 P1 + R-598, the drill's blockers).** The claim page's answers and the Backup page's protection warnings follow the reader's language. **Fourteen** live sites carrying **nine** messages, not the sixteen literals R-596 counted — and the sixteenth, `data["Title"]`, was **DEAD** (`claim.html` is standalone; `.Title` belongs to `layout.html`) and was DELETED rather than translated. `backup_handlers.go` had **nine** code literals, not twelve; three were inside comments. `degradedMessageFor` now returns a **KEY**, so the decision stays language-free in one place while the words are chosen by whoever knows the reader; `buildTierViews`/`backupTargetLabel`/`loadGuestBackup` take `lang` the `buildDataPathCards` way. **The anonymous cookie-less claim page's language chain** (`langFor` → `settings.GetLanguage` → `configLanguage` ← `cfg.Customer.Language`) **was an unpinned assumption and is now a test.** Six existing copy-contract tests were kept rather than weakened — each resolves its key through the real bundle, so they still convict on a reworded Hungarian sentence. **Two things the tests caught and review did not:** an apostrophe in an English value is escaped to `&#39;` and NEVER matches on the page (the failure reads exactly like an unwired handler — R-603), and the i18n gate convicted two of my English sentences for saying "please". **Proven live on guest 9201 through the `felhom_lang` cookie** — and the lockout proved itself unasked: Hungarian attempts locked out the English request from the same source, so the counter is per SOURCE, not per language. **The instrument trap that nearly cost a second fix (R-602): the cookie works only on ANONYMOUS pages** — `langFor` step 2 means a signed-in request reads the household's setting and ignores the cookie, so `/backups?felhom_lang=en` returns HUNGARIAN and reads like an unfixed defect. Use `?lang=` behind auth. **Floor NOT raised — the operator's step**; it still stands at 0.257.0. Guest 9201 is on **felhom-pve**, and **demo-hp answers on no route** (R-601).
+78 -53
View File
@@ -1,66 +1,91 @@
# REPORT — controller v0.259.0: the claim page and the backup warnings in the reader's language # REPORT — controller v0.260.0: a box ahead of the catalog, and a pin that never moves backwards
**R-596 (P1), R-598.** Base `f6909492cc6e` → v0.259.0. MinAgent 0.131.0 unchanged. **R-524 (P2).** Base `19ef0329ab66` → **v0.260.0** (`8f8a64cad7a5`). MinAgent 0.131.0 unchanged.
Architecture read first and named: `felhom.eu/documentation/architecture/09-update-architecture.md`
(§3 the nine decisions, §5.4 the render table, §6.1 slice 4 as shipped, §8 the limitations).
## Claims in the task that turned out wrong ## What was wrong
1. **"Sixteen Hungarian literals reach that page."** Fifteen literal sites reach it, carrying **ten** **Measured, not imagined** — BIGNIGHT Phase 6, 2026-09-15, VM 333. privatebin was updated
distinct messages (four are repeats). Of the fifteen, **one is dead**: `data["Title"]` is read only 2.0.5 → 2.0.6 through the guarded Update; the catalog was then reverted to 2.0.5. At 22:13:37Z the
by `layout.html`, and `claim.html` is standalone with its own bundle-backed `<title>`. So box read `installed privatebin/pdo:2.0.6`, `catalog privatebin/pdo:2.0.5`, and the app page showed
**fourteen live sites, nine messages** were converted and the dead one was deleted. „**Frissítés elérhető — ma**" with a title inviting the household to press Frissítés. The comparison
2. **"`backup_handlers.go` (12 Hungarian literals)."** Nine are code; the other three are Hungarian asked only *does the installed reference DIFFER?*, so **a catalog revert — an operator act on our
inside COMMENTS (`"Mentés most"`, `"Rendszermentés"`, a label quoted in a struct doc). The offer side — presented itself to a customer as an update**, and the guarded Update behind it would have
file's ten is right. advanced the pin 2.0.6 → 2.0.5, onto a datadir the newer version may already have migrated, with §4's
3. **"the recovery code (10 words — find its caller)" in the hub.** The hub does not mint it. ruling saying that cannot be undone.
**felhom-agent** does, in `internal/escrow`, from the **EFF large wordlist** — so the recovery code
**has always been English**, ten words, ≈129 bits. R-597's recovery-code leg needed no work and
this repo does not own that secret. No row was added for it: inventing a second definition here is
how two sources of truth start.
4. **"the mail says 'three words'."** No claim mail states a count; they say `Setup code: %s`. The
only count wording in the product is the **bind page's** passphrase hint/placeholder ("five
words" / „öt szó"). The English pair is now count-free; the Hungarian is untouched.
5. **§16's phone-safe filter** ("no two words differing by one letter within the first six") was
measured before adoption: it removes **5270 of 7772** words, 12.92 → 11.29 bits/word. **Not
adopted** — see the hub REPORT for the three reasons and what replaced it.
6. **Line numbers** in the task were accurate. **"29 633 words"** is right to the line (29 634 lines,
29 609 after dedup). **"`customer.language` reaches the anonymous claim page"** is TRUE and is now
pinned by a test rather than assumed.
7. **"the box checks a hash and needs no change" (for R-597)** is TRUE — verified and pinned by
`TestClaimAcceptsAnEnglishWordCode`.
## What shipped ## What shipped
- `internal/web/claim.go` — 14 sites → `s.msg(r, "claim.msg.*")`; the dead `Title` deleted with the - **`stacks.CatalogOrder`** (`controller/internal/stacks/updateorder.go`) — the comparison gains a
reason recorded in place. fourth verdict (Unknown / Current / Behind / **Ahead**) and **moves out of `web`**. That move is the
- `internal/web/backup_target_offer.go` — three copy constants → bundle keys; `degradedMessageFor` substance: two callers must reach the same verdict — the badge and `Manager.UpdatePreflight` — and a
returns the KEY (decision language-free, in one place); `backupTargetView(ctx, lang)`. comparison implemented twice is a comparison that drifts. `web.compareInstalledToTemplate` is now a
- `internal/web/backup_handlers.go` — `buildTierViews`/`backupTargetLabel`/`loadGuestBackup` take the thin wrapper and keeps every property it had (absent means UNKNOWN and never „Naprakész"; it reads
reader's language, the `buildDataPathCards` shape. `CatalogImages` and never `TemplateImages`; it queries no registry).
- 23 new keys in both bundles; all 23 listed in `scripts/i18n_go_keys.json`. - **The badge.** Ahead reads „Naprakész" / "Up to date", `tag-ok` — the same word and class as level,
because there is nothing for the household to do — with a title that says why
(`badge.update.ahead.title`, born as a key in both bundles). No version number reaches the customer.
- **The refusal.** `UpdatePreflight` returns reason `downgrade`, HTTP 409,
„Ez a változat újabb a katalógusban lévőnél — visszalépés csak az üzemeltető kérésére.", logged with
both image maps. **The API now renders update refusals through `errText`** — without that one line
the new key would have been a seam built and never wired, which is a documented failure class here.
- **Ahead is the NARROW arm.** Every differing service must be orderable AND newer; one older, one
unorderable, and the verdict falls back to Behind — i.e. to v0.233.0..v0.259.0 behaviour. This gate
can BLOCK an update, so it errs towards letting one run.
- **Ordering is `util.Version.Compare` and nothing else** (house rule: one comparator). The new code
is a tag NORMALISER in front of it.
## Evidence ## What the fixture caught that the design did not
| Check | Result | **The first implementation called every real catalog tag unorderable.** It accepted only bare
|---|---| `X.Y`/`X.Y.Z`, and the test fixture uses `nextcloud:31.0.14-apache` — the real catalog pin. The
| `go build ./... && go vet ./... && go test ./...` | green | refusal test failed with `got nil`, and the cause was the code being right about a rule that was
| `controller_gates.py --fast` (17 gates) | all OK | wrong. The rule now takes the version at the FRONT of the tag and requires the trailing suffix to be
| `scripts/i18n_go_parity.py` | OK — 718 keys, byte-for-byte against the frozen base | **identical on both sides**, so `31.0.14-apache → 31.0.15-apache` orders while `26.05.2-ls310 →
| `scripts/i18n_missing_gate.py` | English missing **0** (ceiling 0); Hungarian formal 18 (ceiling 18) | -ls311` (a build number with no rule), `postgres:16-alpine` (a major LINE, not a version),
| `scripts/test_gate_decoys.py` | 23/23 | `kimai/kimai2:apache-2.57.0` (version at the back), a date stamp and a digest pin all stay
unorderable. **Measured against the real catalog: 8 of 66 pins float and one puts its version last.**
**Red-proofs, both seen failing:** ## Red-proofs — three, each SEEN to fail
- One added full stop on `claim.msg.bad_code` in `hu.json` → go-parity named both sides.
- The wrong-code Hungarian literal restored in `claim.go` → the English test convicted twice (the
English sentence absent AND the Hungarian sentence present on the English page).
## Two things the tests caught that review did not | # | the mutation | what failed |
|---|---|---|
| 1 | make `CatalogOrder`'s Ahead arm unreachable | `TestR524_PreflightRefusesDowngrade/ahead` — *"this update must be REFUSED with reason \"downgrade\", got nil"*. The update is ALLOWED and the next thing it does is move the pin back. |
| 2 | treat an UNORDERABLE pair as ahead (`cmp < 0` with the `ok` dropped) | the floating-tag, different-image and digest-pin cases all fail with Ahead — the verdict that would suppress a real „Frissítés elérhető" on the floating pins |
| 3 | delete the ahead arm from `localeFuncs` | `TestUpdateBadgeFollowsTheLanguage` — *"an app ahead of the catalog must carry a badge"* |
- **An apostrophe never renders.** `"The system backup's drive…"` is escaped to `&#39;` by An honest note on #1: the first attempt deleted the preflight block and failed to BUILD (an unused
`html/template` and no `strings.Contains` on the page ever matched it. Reworded; all 23 English import), which proves nothing. It was redone by neutering the Ahead arm instead, and that failed for
values are now free of `' " < > &`. the right reason.
- **"please" is not this product's voice.** The i18n gate convicted two of my English sentences for
pleading. Rewritten to plain second person.
## Open ## A claim in the brief that was wrong, named
Nothing from this release. The live proof and the floor are in the felhom.eu report. **R-589 was NOT open.** The brief said, reviewer-verified, that `updatebadge.go` builds the badge
from four raw Hungarian literals with no key and that R-589 is therefore open. The literals are real;
the conclusion does not follow. They are the Hungarian form and are deliberately frozen — that IS the
parity guarantee — and the ENGLISH form has been rebuilt from the bundle in `web.localeFuncs` since
**v0.258.0**, pinned by `TestUpdateBadgeFollowsTheLanguage` and proven live on a fresh box the same
morning (`DRILL-first-hour-en-0258-2026-09-20.md` item 9). The register row was stale, not the code;
it is closed with that citation. **The general lesson: a reviewer who reads one producer cannot see a
second producer that overrides it.** `updatebadge.go` now says so in its own comment, and v0.260.0's
new arm was written into BOTH producers with a test that fails if either is missing.
## Green gate and delivery
`go build ./... && go vet ./... && go test ./...` — **all green** (full suite, not a subset).
`controller_gates.py --fast` — **17 gates, all OK**; `go-parity` convicted first and was satisfied
properly, by registering both new keys as BORN-AS-KEYS in `i18n_go_keys.json` with the test that pins
each. No `--no-verify`; the pre-push hook ran and passed.
Image `gitea.dooplex.hu/admin/felhom-controller:0.260.0` built and pushed. **Deployed and healthy on
three guests**: demo-felhom 9201, demo-hp 9201, and demo-hp 9202 (the scratch guest, upgraded from
0.245.0 for the live proof).
**The fleet floor was NOT raised — that is the operator's step**, as it was left for v0.258.0 and
v0.259.0; it still stands at 0.257.0. Stated rather than silently skipped: the standing rule asks for
the floor to be raised to deliver a release, and this session deliberately did not, because the two
preceding sessions recorded floor raises as the operator's own act.
Live proof, drift numbers and the state of the whole arc:
`felhom.eu/documentation/audits/UPDATE-ARC-STATE-2026-09-21.md` and `audits/update-arc-2026-09-21/`.