736f54b496
gates / gates (push) Successful in 23s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
90 lines
8.1 KiB
Markdown
90 lines
8.1 KiB
Markdown
# REPORT — controller v0.251.0: nothing decides by reading a Hungarian word (R-553 + R-563)
|
|
|
|
**Date:** 2026-09-17 · **Baseline:** `e236dca48653` (v0.250.0) · **Released:** `0194342` → image
|
|
`gitea.dooplex.hu/admin/felhom-controller:0.251.0` · **MinAgent:** 0.131.0 (unchanged).
|
|
**Architecture read before any claim:** `felhom.eu/documentation/architecture/10-localisation.md` §1/§9
|
|
(§9 rewritten by this session), `07-backup-architecture.md`, `02-controller-module-map.md`.
|
|
**Evidence:** `felhom.eu/documentation/audits/r553-2026-09-17/`.
|
|
|
|
## Claims in the prompt that turned out wrong — named first
|
|
|
|
1. **„`internal/system/mounts_linux.go` L176/L194/L199 produce site 3's warnings."** They do not.
|
|
`h.Warning` from `CheckBackupDestination` never reaches `report.Warnings` — the only producer folded
|
|
into the health report is `checkStoragePaths` (healthcheck.go). `system/` needed no change at all.
|
|
2. **„Site 1 matches `required field` … all producers are ours."** **Nothing produces `required field`.**
|
|
The router matched a phrase no code in this repo writes — a dead arm, removed with the rest.
|
|
3. **Site 3's condition matched more than the prompt implies.** Of the nine warnings that exist today,
|
|
exactly ONE matches („Az adattároló … nem külön meghajtón van"): the three patterns are lower-case
|
|
and every other warning capitalises the word. So the fix had to preserve *one* inline warning, not a
|
|
family — `WarnKindStorageNotSeparate` does exactly that.
|
|
4. **Line numbers drifted** as the prompt warned (router.go:478 → the block moved into a new
|
|
`deployStatusFor`; offbox.go:186/921; alerts.go:208; handlers.go:920/931 → 944).
|
|
5. **„`errors.New(refusal)` at the memory site"** — correct, and it is the reason the memory refusal
|
|
could not be pinned by a unit test: `memoryVerdict` reads the HOST's real memory. Its kind is pinned
|
|
at the source line instead, and the mapping is covered with a translated memory message.
|
|
6. **Confirmed as stated:** site 2's classifier has exactly one caller; `offbox_handlers.go:132` is a
|
|
false positive (an `[INFO]` log line); site 5 is the one rendered byte that changes.
|
|
|
|
## The five sites
|
|
|
|
| # | decision | signal added | producer(s) changed | decision changed | test | HEAD-red → fixed-green |
|
|
|---|---|---|---|---|---|---|
|
|
| 1 | deploy HTTP status | `stacks.ErrRequiredField`, `ErrPathMissing`, `ErrNotEnoughMemory`, `ErrAlreadyDeployed` (`deploy_errors.go`) | `deploy.go` 205/311/326/333/245 | `api/router.go` → new `deployStatusFor` | `TestR553_Deploy_DecisionSurvivesWordingChange`, `TestR553_DeployHandlerUsesTheKind`, `TestR553_DeployProducersCarryKindAndKeepTheirWords` | red: every translated row `= 500, want 400/409`; green after |
|
|
| 2 | off-site failure class | `backup.ErrOffsiteQuota` | `offbox.go` 921 | `ClassifyOffsiteFailure` | `TestR553_OffsiteQuota_{Decision,HeadLine}SurvivesWordingChange`, `TestR553_QuotaProducerKeepsItsWords` | red: translated quota `= "unknown"`, head line „ismeretlen okból"; green after |
|
|
| 3 | disk-warning placement | `monitor.WarnKind*` + `HealthReport.WarningKinds` / `addWarning` / `WarningKindAt` | `healthcheck.go` `checkStoragePaths` | `web/alerts.go` | `TestR553_StorageWarningsCarryKindsAndKeepTheirWords`, `TestR553_DiskWarningPlacementSurvivesWordingChange`, `TestR553_HubReportWarningsAreUnchangedOnTheWire` | red: translated warning `Inline = false`, and an unrelated warning wrongly moved inline; green after |
|
|
| 4 | stale „nothing selected" note | `settings.OffboxTarget.LastWarningKind` + `backup.OffboxWarnNoAppsSelected` | `offbox.go` 1053/1095 (+ both clear sites, both copy sites) | `offboxWarningDisplay` | `TestR553_StaleNote*`, `TestR553_WarningKindIsPersistedAndCopied`, `TestR553_OffboxRunRecordsTheKind` | red: translated note shown verbatim; producer without its kind convicted; green after |
|
|
| 5 | remote-backup poll (R-563) | `data-status="{{.Offbox.LastStatus}}"` | `backups_remote.html` status element | the page's own script | `TestR563_PollStartsFromAttribute` (hu + en), `TestR563_AttributeFollowsTheStatus` | red twice: poll back on the word (both languages), word back inline (English page shows Hungarian); green after |
|
|
|
|
**Message bytes:** every producer's sentence is asserted equal to its pre-change string (sites 1, 2, 4)
|
|
or rendered identically (site 5). `util.KindErrorf` / `KindError` (new, `internal/util/errkind.go`,
|
|
documented in REUSE.md §1) build the same bytes `fmt.Errorf` did and carry the sentinel for `errors.Is`.
|
|
|
|
**External signatures kept, on purpose:** the classifier's restic and ssh arms — `unable to open config
|
|
file`, `is there a repository at the following location`, `produced no snapshots`, `connection refused`,
|
|
`connection reset`, `no route to host`, `i/o timeout`, `timed out`, `permission denied`, `host key`,
|
|
`handshake`, `could not resolve`, `network is unreachable`. We neither write nor translate that output.
|
|
|
|
**The wire:** `internal/report/builder.go` copies Status/Issues/Warnings only; the kinds are internal.
|
|
Pinned by field-set + byte test, and confirmed live (the health block on 0.251.0 carries exactly
|
|
`issues, status, warnings`).
|
|
|
|
## Gates
|
|
|
|
`go build ./... && go vet ./... && go test ./...` → rc 0 before each push. `controller_gates.py --fast`
|
|
→ rc 0. No `--no-verify`. One existing test (`TestClassifyOffsiteFailure_EachCauseIsDistinct`) built the
|
|
quota error the old way and was adapted to build it as the run does — stated here rather than silently.
|
|
|
|
## Live validation (endpoint-level; no browser on DooPlex) — demo-hp guest 9201
|
|
|
|
Deployed by hand; **fleet floor unchanged (0.250.0), no golden.**
|
|
- **Hungarian pages 0.250.0 → 0.251.0:** `/launcher` and `/monitoring` identical; `/backups/remote`
|
|
identical apart from the new attribute and the poll line (+23 bytes); `/dashboard` differs in live
|
|
numbers only.
|
|
- **Deploy refusal:** `POST /api/stacks/adventurelog/deploy` on an already-deployed app → **409 before
|
|
and after with byte-identical message**; the app kept running.
|
|
- **Site 5 live:** `id="offbox-status-value" data-status="ok"` and `v.dataset.status === 'running'`.
|
|
- **Hub:** `health.warnings` `[]` before and after, health keys unchanged.
|
|
- **Not provoked live, and why:** the 400 refusals (a live probe starts an install), the quota failure
|
|
(would need a full quota), the stale note (would need clearing the selection), and the disk-warning
|
|
placement (this box has no storage warning — 0 banner blocks before and after). All four are carried
|
|
by red-proofed tests.
|
|
- **A mistake, and what I did:** the FIRST live probe posted an empty value map to `vaultwarden`, which
|
|
has no empty required field — so the deploy was accepted (202) and the app installed. I stopped it and
|
|
removed it (with the data volume it had just created) within two minutes, verified no container
|
|
remained, and rewrote the probe to the 409 form. Nothing else on the box was touched; the standing
|
|
apps and `bentopdf` were not involved.
|
|
|
|
## Teardown
|
|
|
|
Machine: guest on `hu`, controller 0.251.0; password file and probe scripts shredded/removed; evidence
|
|
pulled off before anything was cleaned. Host: transfer files removed. Hub: nothing written; the DB copy
|
|
shredded. Nothing provisioned.
|
|
|
|
## Observations
|
|
|
|
1. Four more API handlers pick their status by matching ENGLISH internal words (`protected`, `not found`, `not deployed`, `still running`, `not orphaned`). **FILED: R-569**
|
|
2. The stale-note display keeps a Hungarian-text fallback for boxes that have not run off-site on ≥ 0.251.0, and slice 2 must not translate that producer until it goes. **FILED: R-570**
|
|
3. The off-site failure classifier and the alert-placement rule are described in no architecture document. **FILED: R-571**
|
|
4. `required field` was matched by the router and produced by nothing — a dead arm. **NOT-A-FINDING: deleted with the rest of the text chain in this release; nothing to track.**
|
|
5. The i18n inventory's comparison detector, re-run on the fixed tree, reports one Go compare and one indirect compare. **NOT-A-FINDING: the first is the known `[INFO]` log-line false positive, the second is the deliberate legacy fallback carried by R-570; decoys prove the detector still convicts.**
|