REPORT: v0.236.0 (R-442) proven live on demo-hp — data gone, refusal shown, SSD app not refused
gates / gates (push) Failing after 14s
gates / gates (push) Failing after 14s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,204 +1,174 @@
|
||||
# REPORT — v0.235.0, update arc slice 3: freeze the version, keep the fixes flowing (2026-09-06)
|
||||
# REPORT — v0.236.0, R-442: "delete my data too" deletes the data, or says that it could not (2026-09-13)
|
||||
|
||||
*Overwritten each run. This records the most recent implementation only.*
|
||||
|
||||
> **Read this first — one claim in the task was wrong, and one thing this run FOUND that the task did
|
||||
> not anticipate.**
|
||||
>
|
||||
> **The task's §7 Scenario B is incomplete, and the live run is what showed it.** It asks that a
|
||||
> catalog version move freezes the app to "the **stored applied definition**", and says nothing about
|
||||
> keeping that store current. But the store is written when the PIN is written, so a fix delivered
|
||||
> afterwards (Scenario A's own case) lands in the live file and **not** in the store — and the first
|
||||
> freeze then reverts it. **Observed live at 08:20:29Z**: the frozen file came back with
|
||||
> `interval: 30s`, nineteen minutes after `45s` had been delivered. Left unfixed, slice 3 would have
|
||||
> silently undone the half of the ruling that says fixes keep flowing. **The equal-images branch now
|
||||
> refreshes the store as it delivers.** Everything else in §5's symbol table was accurate; the
|
||||
> remaining corrections are in §10.
|
||||
## For the operator — what changed, in plain words
|
||||
|
||||
---
|
||||
When a customer removes an app and ticks *also delete my data*, the box now deletes the data and lists
|
||||
what went. If the box cannot work out where the data is, it refuses, keeps the app, and says so in one
|
||||
sentence. It never again reports success over data left behind. Proven on the HP with a throwaway
|
||||
Nextcloud. Live on both demo machines. Nothing is waiting on you.
|
||||
|
||||
## 1. Confirmed baselines — none had moved
|
||||
## Claims in the task that turned out wrong or incomplete — named first
|
||||
|
||||
| repo | task's baseline | found |
|
||||
|---|---|---|
|
||||
| felhom-controller | `998aa319588f` | `998aa319588f` |
|
||||
| felhom.eu | `bc47dd4ef997` | `bc47dd4ef997` |
|
||||
| app-catalog-felhom.eu | `8220f8d82e53` | `8220f8d82e53` |
|
||||
| felhom-agent | — | untouched |
|
||||
1. **"Three lookups" was right, but the backup half needed the same fix, not only the same honesty.**
|
||||
Under the global lookup, every `remove_backups: true` on every box was refused — not only paths
|
||||
"outside the expected directory": with `hddPath == ""` the base was `/backups`, so nothing could
|
||||
ever be under it. Making that refusal *visible* without fixing its *base* would have shown every
|
||||
customer a wrong refusal. The base now follows the same per-app rule (the app's namespace root:
|
||||
its own drive, or `<system data>/felhom-data` otherwise), which is exactly what the router's
|
||||
`AppNamespaceRoot` produced the paths from. **This is one deliberate step beyond "and no more"**
|
||||
(task §2, "the same honesty treatment and no more"), and the reason is above.
|
||||
2. **"Deploy a small drive-backed app" — 8 of the 13 `needs_hdd` catalog apps have no `${HDD_PATH}`
|
||||
bind at all.** They bind only `${USERDATA_PATH}` (the shared library) and named volumes. For them
|
||||
"delete my data" correctly removes nothing on the drive, and the modal shows no checkbox. The
|
||||
faithful throwaway was Nextcloud — the app R-442 was measured on.
|
||||
3. **The task's §11 verifies the deploy on `felhom-pve` 9201 and validates on `demo-hp`.** Both guests
|
||||
were deployed; the live validation ran on demo-hp as specified.
|
||||
4. **"NOT ESTABLISHED: whether the fleet shares this config shape"** (the R-442 row) — now
|
||||
established: demo-felhom also has 0 `hdd_path` lines and 0 `FELHOM_PATHS_*` variables.
|
||||
|
||||
Highest `R-` id: **457**, confirmed. Minted **R-458**.
|
||||
## Baselines (verified before starting)
|
||||
|
||||
## 2. Files created and modified
|
||||
|
||||
**Created:** `controller/internal/stacks/pin.go`, `controller/internal/stacks/pin_test.go`,
|
||||
`controller/internal/sync/render_test.go`.
|
||||
|
||||
**Modified:** `controller/internal/stacks/deploy.go` (the `PinnedImages` field + the deploy writer),
|
||||
`controller/internal/stacks/manager.go` (`Stack.CatalogImages`, `ScanStacks`, `UpdateStack`),
|
||||
`controller/internal/sync/sync.go` (the seam + the render table),
|
||||
`controller/internal/web/updatebadge.go`, `controller/internal/web/updatebadge_test.go`,
|
||||
`controller/cmd/controller/main.go` (the seam, adoption, and the startup ordering), plus `CHANGELOG.md`,
|
||||
`CONTEXT.md`, `REUSE.md`, `controller/README.md`.
|
||||
|
||||
## 3. Commits pushed to `main`
|
||||
|
||||
| repo | commit | what |
|
||||
|---|---|---|
|
||||
| felhom-controller | **`8a0e0a59adc7`** | v0.235.0 — the pin, the render, adoption, the badge fix |
|
||||
| felhom-controller | **`2a56f557d048`** | the stored definition must follow a delivered fix (found live) |
|
||||
| app-catalog-felhom.eu | `dc7e548` / `09b4ff5` | the two live-test pushes |
|
||||
| app-catalog-felhom.eu | `1798ce6` / `17cc784` / `7b9b9b3` | their reverts, and the CHANGELOG entry recording them as a measurement |
|
||||
| felhom.eu | `417df06f3529` | the architecture doc, module map, register, roadmap, capability map, STATUS, live evidence |
|
||||
|
||||
No branches. All gate runs passed on push.
|
||||
|
||||
## 4. Tests: 1729 → 1746 (+17). 28 packages, 0 FAIL.
|
||||
|
||||
`go build ./... && go vet ./... && go test ./...` green in the controller module.
|
||||
|
||||
| group | what it pins |
|
||||
|---|---|
|
||||
| **A** | a non-image template change reaches a pinned, matching app |
|
||||
| **B** | an image change freezes it — and the file content is the **stored definition**, with the new template's body (`NEXTCLOUD_TRUSTED_DOMAINS`) asserted **absent** |
|
||||
| **C** | self-healing from a corrupted file in **both** branches |
|
||||
| **D** | the update advances the pin, re-renders and stores, all before the pull; and **refuses** when the catalog cannot be read, while leaving an unpinned app alone |
|
||||
| **E** | adoption skips an incomplete observation and a complete-but-mismatched one, manufactures no applied file, is idempotent, and runs **no docker command at all** |
|
||||
| **F** | a restore's pin is reported to the syncer with its stored definition (R-441's contract) |
|
||||
| **G** | the badge reads the catalog, not the rendered file; no catalog entry renders nothing |
|
||||
| **H** | the AST walk: the seam and adoption are wired, **and their order** against the backfill and `syncer.Start()` |
|
||||
| — | the render table's remaining rows, the nil seam, and an empty stored definition |
|
||||
|
||||
### All three companion red-proofs — mutation, observed failure, revert
|
||||
|
||||
| # | mutation | observed failure | reverted |
|
||||
| Repo | `main` before | after | version |
|
||||
|---|---|---|---|
|
||||
| **1** | `renderSource` returns the catalog template on the moved branch | `TestGroupB` — *"a pinned app must NOT receive the catalog's new version"* | yes |
|
||||
| **2** | `observationCoversTemplate` guard removed from `AdoptPins` | `TestGroupE/incomplete_observation` — *"pinned 1, want 0 — only 1 of 2 services was observed"* | yes |
|
||||
| **3** | `compareInstalledToTemplate` reads `TemplateImages` again | `TestGroupG` — *"THE FEATURE IS INVERTED"*, and *„Naprakész"* with no catalog entry | yes |
|
||||
| felhom-controller | `bab82c471e03` | `42a73e6` (code) + this REPORT commit | v0.235.0 → **v0.236.0** |
|
||||
| felhom.eu | `d6837d98ee24` | docs commit (see that repo's log) | — |
|
||||
|
||||
Full suite re-run green after each revert.
|
||||
Highest `R-` id before: 464. **No new row minted.** R-442 closed.
|
||||
|
||||
**A fourth defect was caught by a test rather than by review:** the syncer trusted the applied path it
|
||||
was handed and would have written an **empty compose file over a live app**. It now re-reads and falls
|
||||
back to the catalog.
|
||||
## Files
|
||||
|
||||
## 5. Deployed version
|
||||
- `controller/internal/stacks/delete.go` — `appHDDPath`, `composeBindsDrive`, `hddPathForRemoval`,
|
||||
`hddNoteFor`; `RemoveRefusedError` + reasons; the three `m.cfg.Paths.HDDPath` reads replaced;
|
||||
`[]` init; `hdd_paths_missing`, `hdd_note`, `backup_paths_refused`; backup base via
|
||||
`appbackup.NamespaceRootFor`.
|
||||
- `controller/internal/api/router.go` — `errors.As` → 409 + `Message` in `removeStack` and `deleteStack`.
|
||||
- `controller/internal/web/templates/layout.html` — both success modals render `hdd_note` and
|
||||
„Nem törölt mentések".
|
||||
- Tests: `controller/internal/stacks/delete_r442_test.go` (13), `controller/internal/api/remove_r442_test.go` (2).
|
||||
- Docs: `CHANGELOG.md` (v0.236.0), `CONTEXT.md`, `REUSE.md` (row 120), `controller/README.md` (3 rows).
|
||||
|
||||
## Design rule this brings the removal path into line with
|
||||
|
||||
`07-backup-architecture.md` ~L437, *"[DESIGN] 2026-08-22 — the restore destination is resolved by the
|
||||
same rule as the capture destination: the drive if the app declares one (`HDD_PATH`), the system data
|
||||
path otherwise."* Deploy (`withPathVars`), the start gate (`startGatedByMissingDrive`) and the backup
|
||||
destination (`GetAppDrivePath`) already read the app's own record; removal was the odd one out.
|
||||
**No fallback to the global** — the helper's comment says why.
|
||||
|
||||
## Tests — 1761 in the module, 15 new, all green; gates all OK
|
||||
|
||||
| Scenario | Test | Asserts (the effect) | Result |
|
||||
|---|---|---|---|
|
||||
| A drive app, data removed | `TestRemoveStack_R442_RemovesDeclaredDriveData` | folder gone from a real tree; listed with size; app.yaml gone; compose ran; `appdata/` root intact | PASS |
|
||||
| B same app, data kept | `…KeepsDataWhenNotAsked` | file present; listed under preserved; `"hdd_paths_removed":[]` on the wire | PASS |
|
||||
| **C unresolvable** | `…RefusesWhenDriveUnresolvable` | typed error, reason, exact sentence; **compose marker absent**; app.yaml present | PASS |
|
||||
| **D SSD app** | `…SSDAppIsNotRefused` | no error; non-nil empty list; `[]` on the wire; note; app removed | PASS |
|
||||
| drive absent | `…RefusesWhenDriveAbsent` | drive-absent sentence naming the path; nothing ran; app kept | PASS |
|
||||
| keep-data never refused | `…KeepDataNeverRefusedOnHDDGrounds` | drive absent + `false` → removed | PASS |
|
||||
| folder already gone | `…MissingFolderIsStatedNotRefused` | `hdd_paths_missing`, note names it, app removed | PASS |
|
||||
| E backup half | `…BackupRefusalReachesResponse` | inside path removed + listed; outside path KEPT + `backup_paths_refused` | PASS |
|
||||
| E SSD app backups | `…SSDAppBackupsUnderSystemNamespace` | `<sys>/felhom-data/backups/…` removable | PASS |
|
||||
| `${USERDATA_PATH}` convention | `…UserdataConventionFromPerAppPath` | appdata removed, shared `userdata/` untouched; `ExportDataMounts` from the per-app path includes `<hdd>/userdata` | PASS |
|
||||
| orphan delete C / A | `TestDeleteStack_R442_*` (2) | same shapes for `DeleteStack` | PASS |
|
||||
| modal source | `TestGetStackHDDData_R442_ReadsPerAppRecord` | lists the folder while the global stays empty | PASS |
|
||||
| **production wiring** | `TestRemoveHandler_R442_*` (2) | real handler → real `NewManager` + `ScanStacks` → **409**, exact sentence / drive-absent sentence, app.yaml still on disk | PASS |
|
||||
|
||||
**Red-proof 1 (Scenario C — the pre-fix silent fallback to the global, and nil lists):** 5 tests
|
||||
failed, with the wrong values visible:
|
||||
`want *RemoveRefusedError, got err=<nil> resp.hdd_paths_removed=[] app.yaml present=false — a 200 over inaction`;
|
||||
handler: `HTTP 200 ok=true — a 2xx over a removal that could not resolve the data location`;
|
||||
B: `got {"…","hdd_paths_removed":null,…}`. Fix restored → green.
|
||||
|
||||
**Red-proof 2 (Scenario D — make "declares no drive" refuse):** 2 tests failed:
|
||||
`an SSD app must not be refused on HDD grounds, got: Az alkalmazás adatainak helye nem állapítható meg…`.
|
||||
Fix restored → green. `git diff` clean after both.
|
||||
|
||||
Green gate: `go build ./... && go vet ./... && go test ./...` rc=0. `controller_gates.py --fast`: all OK.
|
||||
|
||||
## Deployed
|
||||
|
||||
`gitea.dooplex.hu/admin/felhom-controller:0.236.0` — `Up … (healthy)` on **both** demo guests
|
||||
(felhom-pve 9201 and demo-hp 9201), verified with `docker ps`.
|
||||
|
||||
## Live validation on demo-hp — endpoint-level (the exact endpoints the UI's modal and buttons invoke: `GET …/hdd-data`, `GET …/backup-data`, `POST …/stop`, `POST …/remove`), driven from inside guest 9201 with the session cookie + `X-CSRF-Token`. Evidence: `felhom.eu/documentation/audits/R442-2026-09-13/`.
|
||||
|
||||
**Scenario A — Nextcloud, deployed through the real deploy endpoint, data written BY THE APP (its
|
||||
first-run install: 69 files, 63 MB under `/mnt/felhom-drives/hdd_1/appdata/nextcloud`), stopped,
|
||||
removed with `{"remove_hdd_data":true,"remove_backups":true}`:**
|
||||
|
||||
```
|
||||
$ ssh hp "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'"
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.235.0 Up 31 minutes (healthy)
|
||||
{"ok":true,"data":{"removed":"nextcloud","volumes_removed":null,
|
||||
"hdd_paths_removed":["/mnt/felhom-drives/hdd_1/appdata/nextcloud (63M)"],
|
||||
"hdd_paths_preserved":[],
|
||||
"backup_paths_removed":["/mnt/felhom-drives/hdd_1/backups/primary/nextcloud/db-dumps (8.0K)"]},
|
||||
"message":"Stack nextcloud removed"} HTTP 200
|
||||
ls: cannot access '/mnt/felhom-drives/hdd_1/appdata/nextcloud': No such file or directory
|
||||
ls /mnt/felhom-drives/hdd_1/appdata → paperless romm
|
||||
[INFO] Removed HDD data: /mnt/felhom-drives/hdd_1/appdata/nextcloud (63M)
|
||||
```
|
||||
The db-dumps entry was a **planted** fixture file (`r442-fixture.sql`, stated) — no nightly had run
|
||||
for a 3-minute-old app. The modal's `GET …/hdd-data` beforehand listed the folder with `63M` — under
|
||||
0.235.0 that endpoint returned nothing for the same app.
|
||||
|
||||
**Scenario C — Nextcloud redeployed, stopped, then `HDD_PATH` deleted from its `app.yaml` (a
|
||||
test-only edit, backed up and restored), `{"remove_hdd_data":true}`:**
|
||||
|
||||
```
|
||||
{"ok":false,"error":"Az alkalmazás adatainak helye nem állapítható meg, ezért semmit nem töröltünk. Az alkalmazás nem lett eltávolítva."} HTTP 409
|
||||
state=stopped deployed=True app.yaml present (1302 B) data files after: 69 (before: 69)
|
||||
[ERROR] [stacks] RemoveStack nextcloud refused: data removal requested, the compose binds a drive path, but app.yaml records no HDD_PATH — nothing removed, app kept (R-442)
|
||||
```
|
||||
`app.yaml` restored → the **same call** returned HTTP 200 with the 63M folder listed and gone
|
||||
(positive control: the refusal was about the record, not the app).
|
||||
|
||||
**Scenario D — Gokapi (SSD-only: 0 `HDD_PATH` lines in app.yaml, 0 drive binds in compose),
|
||||
`{"remove_hdd_data":true}`:**
|
||||
|
||||
```
|
||||
{"ok":true,"data":{"removed":"gokapi","volumes_removed":null,"hdd_paths_removed":[],"hdd_paths_preserved":[],
|
||||
"hdd_note":"Az alkalmazás nem tárolt saját adatot külső meghajtón, így ott nem volt mit törölni."},…} HTTP 200
|
||||
```
|
||||
|
||||
Previous: `0.234.0`. Fleet after the run: **21 containers up, none unhealthy.**
|
||||
**ASCII-fragment controls** (`controls.txt`, counted on DooPlex over the saved bodies; `llap` is the
|
||||
ASCII core of *állapítható*): C body = 2 (the JSON error + the router's ERROR line), A body = 0,
|
||||
D body = 0 (in-guest count; the DooPlex file shows 1 from my own label line). Positive control that the
|
||||
grep works: `hdd_paths_removed` = 1 in every file. `HTTP 409` appears only in C.
|
||||
|
||||
## 6. Live evidence
|
||||
Not validated live: Scenario B (kept data) and the drive-absent refusal — unit-tested only; no drive
|
||||
was unplugged on the demo box.
|
||||
|
||||
Full quotes: `felhom.eu/documentation/tests/VALIDATION-update-slice3-2026-09-06.md`.
|
||||
**Method: endpoint level, plus two REAL catalog pushes travelling the REAL 15-minute cycle** — a
|
||||
hand-edited file on the box would have proved nothing about a change to the syncer.
|
||||
## Teardown — three layers
|
||||
|
||||
**Adoption:** `9 pinned, 0 already pinned, 0 left unpinned`, multi-service apps pinned per service.
|
||||
1. **Apps:** both throwaway apps (`nextcloud`, `gokapi`) were removed BY THE FEATURE UNDER TEST; all
|
||||
nine standing apps still deployed (`bentopdf bookstack calibre-web docmost kimai opengist
|
||||
paperless-ngx privatebin romm`); `bentopdf` kept.
|
||||
2. **Residue:** the deploy's recovery-unit capture left `backups/primary/nextcloud/{compose/,manifest.json}`
|
||||
(not customer data; removal deletes only `db-dumps/` by design) — removed BY HAND, stated here.
|
||||
`appdata/` holds only `paperless`, `romm` — unchanged.
|
||||
3. **Helpers:** `/tmp/r442.sh`, `/tmp/r442.pw`, `/tmp/r442-*.json` deleted from guest and host
|
||||
(count 0). Nothing else was provisioned. The password file travelled file→file and was never printed.
|
||||
|
||||
**Scenario A** — catalog `dc7e548` (healthcheck 30s → 45s, no image):
|
||||
```
|
||||
08:01:51 [INFO] [sync] Updated bentopdf/docker-compose.yml
|
||||
live: image …:v2.8.6 interval: 45s container: v2.8.6, started 03:30:20Z (untouched)
|
||||
```
|
||||
## Rows
|
||||
|
||||
**Scenario B** — catalog `09b4ff5` (v2.8.6 → v2.8.5):
|
||||
```
|
||||
08:20:29 [INFO] [sync] Updated bentopdf/docker-compose.yml
|
||||
catalog: v2.8.5 live: v2.8.6 pin: v2.8.6
|
||||
```
|
||||
and then `POST /api/stacks/bentopdf/restart`:
|
||||
- **R-442 → CLOSED** (`CLOSED-ITEMS.md`, top). **Opened: R-465** (the six remaining `Paths.HDDPath` readers — audit), **R-466** (recovery-unit residue after "delete backups"), **R-467** (v0.236.0 owes a golden) — all P3-LOW, owner CC. Register: OPEN 208 → 210, CLOSED 168 → 169.
|
||||
- `00-capability-map.md` lifecycle row narrowed (remove proved the app, not the data) and re-proven.
|
||||
- `STATUS.md`: item 13 (plain language) + **item 7 closed** (ruled 2026-09-02, `09` §3).
|
||||
- Highest `R-` id now 467.
|
||||
|
||||
| | before v0.235.0 (spike §2) | now |
|
||||
|---|---|---|
|
||||
| elapsed | **18.3 s** | **0.1 s** |
|
||||
| pulled? | yes, v2.8.5 entered the local store | **no** — `docker images` lists only v2.8.6 |
|
||||
| container | recreated | **not recreated**, `started` unchanged |
|
||||
| digest | changed | `sha256:eaeea1e4…`, identical to baseline |
|
||||
## Observations
|
||||
|
||||
**Scenario D** — `POST …/update`, 16.7 s:
|
||||
```
|
||||
08:22:21 update bentopdf: pin advanced to the catalog's current definition (…:v2.8.5)
|
||||
08:22:38 Stack bentopdf updated successfully (took 16.6s)
|
||||
container v2.8.5, digest sha256:2d867aac… live/pin/applied all v2.8.5
|
||||
```
|
||||
The pin advanced **17 s before** the update completed — i.e. before the pull, as the design requires.
|
||||
|
||||
**Scenario G** — quoted from the live page while frozen:
|
||||
```html
|
||||
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. …">Frissítés elérhető — 56 napja</span>
|
||||
```
|
||||
ASCII fragments (`grep -oF`): `Naprak` **0** on the frozen app page and **8** on the list (the eight
|
||||
current apps); `napja` **1**; positive control `BentoPDF` 4; negative controls `zzz-never-present` and
|
||||
`Nem-karbantartott-XYZ` both **0**.
|
||||
|
||||
**The freeze holds in BOTH directions:** with the catalog reverted to v2.8.6 and the app pinned to
|
||||
v2.8.5, the sync logged `hash match, skipped` and the app stayed on v2.8.5. The pin is what the
|
||||
customer HAS, not what is newest.
|
||||
|
||||
**Teardown:** a final Update returned the container to `sha256:eaeea1e4…` — **byte-identical to the
|
||||
pre-run baseline** — with `interval: 30s` restored. Both catalog commits reverted; the catalog tree is
|
||||
byte-identical to `8220f8d`. **This run provisioned nothing.**
|
||||
|
||||
## 7. R-441 closed by MEASUREMENT
|
||||
|
||||
The restore writer now pins to the unit's captured compose and stores it, so the render obeys the
|
||||
restored definition. **The closure rests on the render behaviour being measured live** (§6 Scenarios B
|
||||
and 6b — a pinned app's file surviving a sync that would previously have overwritten it within 15
|
||||
minutes), plus `TestGroupF` for the contract the syncer relies on. **What was NOT done: a full live
|
||||
restore.** That needs a restore rehearsal, which is a phase, not a check — named in §8.
|
||||
|
||||
## 8. NOT yet live-validated — an explicit list
|
||||
|
||||
1. **A live RESTORE setting the pin.** The mechanism is measured; this specific entry point is not.
|
||||
2. **The adoption skips** — on demo-hp all nine apps were complete and matching, so `0 left unpinned`.
|
||||
The skip branches are unit-tested with a red-proof; no live app exercised them.
|
||||
3. **The `no stored definition` render row.** Every pinned app on the box has one.
|
||||
4. **A multi-service app through a freeze.** bentopdf is single-service; the multi-service half is
|
||||
covered by adoption (docmost, paperless-ngx, romm pinned per service) and by unit tests.
|
||||
5. **Any box other than demo-hp.** demo-felhom is still on 0.234.0.
|
||||
|
||||
## 9. Register — 203 open before, 203 after; closed 163 → 167
|
||||
|
||||
**Closed:** R-447 (slice 3 shipped), R-441 (by measurement), R-438 (both halves discharged), R-455
|
||||
(the operator added a Docker Hub PAT). **Opened:** R-458 — `.felhom.yml` keeps flowing to a frozen app
|
||||
(P3-LOW, CC), with what would settle it by measurement rather than code. All four compressed into
|
||||
`CLOSED-ITEMS.md`, each naming `bc47dd4ef997`.
|
||||
|
||||
## 10. Every claim in the task that turned out to be wrong, named
|
||||
|
||||
1. **§7 Scenario B is incomplete** — see the box at the top. The stored definition must FOLLOW a
|
||||
delivered fix, or the first freeze reverts it. Found live, not by review.
|
||||
2. **§5 says "the syncer must not import the stack manager".** It now imports the `stacks` PACKAGE for
|
||||
two pure symbols — `RenderPlan` and `ParseComposeImages` — and never touches `Manager` or
|
||||
`app.yaml`. The alternative was a second compose-image parser, which is the duplication `REUSE.md`
|
||||
exists to prevent. **The intent is honoured; the letter is not, and the import carries a comment
|
||||
saying so.**
|
||||
3. **§2.2 says to call adoption "right after `BackfillInstalledImages()`" and stops there.** That is
|
||||
necessary but not sufficient: `syncer.Start()` fires an immediate sync, and at its original
|
||||
position (~L392) that first sync would have run while every app was still unpinned — copying the
|
||||
catalog over a deployed app **once per boot**. `Start()` was moved to after adoption; the order is
|
||||
pinned by `TestGroupH`.
|
||||
4. **All line-number landmarks were accurate** (`AppConfig` ~L100, `ScanStacks` ~L468 with
|
||||
`TemplateImages` at ~L539/~L550, the syncer wiring ~L389, `RecreateStackDefinitionFromUnit`
|
||||
~L2582), and **§3's warning was exactly right** — the badge would have inverted silently, and
|
||||
red-proof 3 shows it doing so.
|
||||
|
||||
## 11. Observations — noticed, documented, NOT acted on
|
||||
|
||||
1. **The syncer's DEBUG hash line now prints two identical hashes and the word `(changed)`.**
|
||||
`logFileHashes` re-reads src and dst *after* the copy, so they always match; with the render, `src`
|
||||
is sometimes the applied file, which makes the oddity conspicuous. Cosmetic, DEBUG-only, and
|
||||
pre-existing. **NOT-A-FINDING: it misleads no verdict — the `Updated <app>/<file>` INFO line above
|
||||
it is the real signal, and changing a debug helper inside a release about the syncer would add
|
||||
unreviewed noise to the one path that touches every app on every box.**
|
||||
2. **An app pinned before v0.235.0's refresh logic shipped carries a store one fix behind** until its
|
||||
next delivered fix, which self-corrects it. Observed on bentopdf at 08:20:29. **NOT-A-FINDING: the state converges by itself on the next delivered fix, and the only
|
||||
alternative — rewriting every stored definition at boot — would touch every app on every box to
|
||||
correct something that costs nothing.**
|
||||
3. **The golden is three releases behind** (`0.232.0` vs `0.235.0`), per the push advisory.
|
||||
**NOT-A-FINDING: the `golden-notice` gate raises it on every release and `STATUS.md` carries the
|
||||
debt; a register row would duplicate an instrument that already fires.**
|
||||
1. **`cfg.Paths.HDDPath` still has readers** — `report/builder.go:69`, `monitor/healthcheck.go:35`,
|
||||
`api/router.go` system-info, `web/server.go:740`, `main.go` (auto-discovery seed + metrics). Each
|
||||
reads an always-empty value on the fleet; whether any of them is silently inert the way removal was
|
||||
is worth one short audit. Not touched here. FILED: R-465
|
||||
2. **Removal with "delete backups" leaves the recovery unit's `compose/` + `manifest.json`** — the
|
||||
router passes only `AppDBDumpPath`. Small, pre-existing; a customer who asked for backups gone is
|
||||
left with a manifest. FILED: R-466
|
||||
3. **8 of 13 `needs_hdd` apps bind only `${USERDATA_PATH}`**, so their modal never offers the data
|
||||
checkbox. Correct by the placement design (`01` — userdata is the shared library), but the label
|
||||
„Felhasználói adatok a merevlemezen" only ever appears for the 5 that bind `${HDD_PATH}`. NOT-A-FINDING: this IS the `01` placement design — userdata is the customer's shared library, and an app removal deleting it would destroy media other apps and the customer own; the modal correctly offers nothing.
|
||||
4. `app.yaml` carries `HDD_PATH` twice (`env:` and `locked_fields:`); the test-only edit removed the
|
||||
`env:` line only, which is the one `appHDDPath` reads — proven by the refusal firing. NOT-A-FINDING: `locked_fields` is a list of field NAMES (which fields are locked after deploy), not a second value; nothing reads a path from it.
|
||||
5. **v0.236.0 owes a golden** (the golden-notice gate, advisory): newest golden baked is 0.232.0. A bake + vouch is the `RUNBOOK-manual-build.md` §4.1 procedure and is outside this task; until then a fresh install receives 0.232.0 and picks up 0.236.0 by self-update. FILED: R-467
|
||||
|
||||
Reference in New Issue
Block a user