Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -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**
|
||||
distinct messages (four are repeats). Of the fifteen, **one is dead**: `data["Title"]` is read only
|
||||
by `layout.html`, and `claim.html` is standalone with its own bundle-backed `<title>`. So
|
||||
**fourteen live sites, nine messages** were converted and the dead one was deleted.
|
||||
2. **"`backup_handlers.go` (12 Hungarian literals)."** Nine are code; the other three are Hungarian
|
||||
inside COMMENTS (`"Mentés most"`, `"Rendszermentés"`, a label quoted in a struct doc). The offer
|
||||
file's ten is right.
|
||||
3. **"the recovery code (10 words — find its caller)" in the hub.** The hub does not mint it.
|
||||
**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`.
|
||||
**Measured, not imagined** — BIGNIGHT Phase 6, 2026-09-15, VM 333. privatebin was updated
|
||||
2.0.5 → 2.0.6 through the guarded Update; the catalog was then reverted to 2.0.5. At 22:13:37Z the
|
||||
box read `installed privatebin/pdo:2.0.6`, `catalog privatebin/pdo:2.0.5`, and the app page showed
|
||||
„**Frissítés elérhető — ma**" with a title inviting the household to press Frissítés. The comparison
|
||||
asked only *does the installed reference DIFFER?*, so **a catalog revert — an operator act on our
|
||||
side — presented itself to a customer as an update**, and the guarded Update behind it would have
|
||||
advanced the pin 2.0.6 → 2.0.5, onto a datadir the newer version may already have migrated, with §4's
|
||||
ruling saying that cannot be undone.
|
||||
|
||||
## What shipped
|
||||
|
||||
- `internal/web/claim.go` — 14 sites → `s.msg(r, "claim.msg.*")`; the dead `Title` deleted with the
|
||||
reason recorded in place.
|
||||
- `internal/web/backup_target_offer.go` — three copy constants → bundle keys; `degradedMessageFor`
|
||||
returns the KEY (decision language-free, in one place); `backupTargetView(ctx, lang)`.
|
||||
- `internal/web/backup_handlers.go` — `buildTierViews`/`backupTargetLabel`/`loadGuestBackup` take the
|
||||
reader's language, the `buildDataPathCards` shape.
|
||||
- 23 new keys in both bundles; all 23 listed in `scripts/i18n_go_keys.json`.
|
||||
- **`stacks.CatalogOrder`** (`controller/internal/stacks/updateorder.go`) — the comparison gains a
|
||||
fourth verdict (Unknown / Current / Behind / **Ahead**) and **moves out of `web`**. That move is the
|
||||
substance: two callers must reach the same verdict — the badge and `Manager.UpdatePreflight` — and a
|
||||
comparison implemented twice is a comparison that drifts. `web.compareInstalledToTemplate` is now a
|
||||
thin wrapper and keeps every property it had (absent means UNKNOWN and never „Naprakész"; it reads
|
||||
`CatalogImages` and never `TemplateImages`; it queries no registry).
|
||||
- **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 |
|
||||
|---|---|
|
||||
| `go build ./... && go vet ./... && go test ./...` | green |
|
||||
| `controller_gates.py --fast` (17 gates) | all OK |
|
||||
| `scripts/i18n_go_parity.py` | OK — 718 keys, byte-for-byte against the frozen base |
|
||||
| `scripts/i18n_missing_gate.py` | English missing **0** (ceiling 0); Hungarian formal 18 (ceiling 18) |
|
||||
| `scripts/test_gate_decoys.py` | 23/23 |
|
||||
**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
|
||||
refusal test failed with `got nil`, and the cause was the code being right about a rule that was
|
||||
wrong. The rule now takes the version at the FRONT of the tag and requires the trailing suffix to be
|
||||
**identical on both sides**, so `31.0.14-apache → 31.0.15-apache` orders while `26.05.2-ls310 →
|
||||
-ls311` (a build number with no rule), `postgres:16-alpine` (a major LINE, not a version),
|
||||
`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:**
|
||||
- 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).
|
||||
## Red-proofs — three, each SEEN to fail
|
||||
|
||||
## 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 `'` by
|
||||
`html/template` and no `strings.Contains` on the page ever matched it. Reworded; all 23 English
|
||||
values are now free of `' " < > &`.
|
||||
- **"please" is not this product's voice.** The i18n gate convicted two of my English sentences for
|
||||
pleading. Rewritten to plain second person.
|
||||
An honest note on #1: the first attempt deleted the preflight block and failed to BUILD (an unused
|
||||
import), which proves nothing. It was redone by neutering the Ahead arm instead, and that failed for
|
||||
the right reason.
|
||||
|
||||
## 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/`.
|
||||
|
||||
Reference in New Issue
Block a user