2fb558552a
gates / gates (push) Successful in 13s
The record: proven on demo-hp through the boot reconciler (a real production caller, no hand-set state) on a single-service AND a multi-service app, with all three digests matching ground truth read independently beforehand. The badge render: NOT validated. The vaulted dashboard password is stale on BOTH demo controllers; the five attempts are listed rather than summarised, and the controller's own log is the discriminator that says wrong password, not wrong host header. Filed as R-453 and raised in STATUS.md item 9. Also recorded: the build was blocked by a Docker Hub 429 and the base images came from Google's Hub mirror, with both digests written down so the identity check is one command when the throttle clears - KNOWN, not measured here.
240 lines
16 KiB
Markdown
240 lines
16 KiB
Markdown
# REPORT — v0.233.0, update arc slices 1 & 2 (2026-09-02)
|
|
|
|
*Overwritten each run. This records the most recent implementation only.*
|
|
|
|
> **Read this first: one claim in the task turned out to be wrong, and it is a path, not a fact.**
|
|
> The task cites source as `internal/stacks/deploy.go`, `internal/web/funcmap.go` and so on. **The Go
|
|
> module lives under `controller/`** — every one of those is `controller/internal/...`. **Every line
|
|
> number in the task was EXACT against the baseline** (`AppConfig` 99, `runComposeDeploy` 395,
|
|
> `LoadAppConfig` 792, `SaveAppConfig` 806, `SensitiveEnvVars` 889, `stackEnv` 1233,
|
|
> `composeExecCustomEnv` 1268, `execCommand` 1344, `logPostStartStatus` 1382, `checkLocalImages` 1410,
|
|
> `Metadata` 14, `isOperationalState` 90) — checked, not assumed. The rest of §10 is at the end.
|
|
|
|
---
|
|
|
|
## 1. Confirmed baselines — none had moved
|
|
|
|
| repo | task's baseline | found | note |
|
|
|---|---|---|---|
|
|
| felhom-controller | `960d29b0612c` | `960d29b0612c` | matched |
|
|
| felhom.eu | `56c7e373a3e2` | `56c7e373a3e2` | matched |
|
|
| app-catalog-felhom.eu | `5d8f25f61189` | `5d8f25f61189` | matched |
|
|
| felhom-agent | `4586f0f7f6d1` | `4586f0f7f6d1` | **not touched** |
|
|
|
|
Highest `R-` id: **445**, confirmed. Minted **R-446..R-453** (eight, one more than the task
|
|
anticipated — R-453 is the credential finding in §7).
|
|
|
|
## 2. Files created and modified
|
|
|
|
**Created:** `controller/internal/stacks/installed.go`, `controller/internal/stacks/installed_test.go`,
|
|
`controller/internal/web/updatebadge.go`, `controller/internal/web/updatebadge_test.go`.
|
|
|
|
**Modified:** `controller/internal/stacks/deploy.go` (the two new `AppConfig` fields + the deploy-path
|
|
call), `controller/internal/stacks/manager.go` (the seam field, `Stack.TemplateImages`, `ScanStacks`,
|
|
three call sites), `controller/internal/stacks/metadata.go` (`CatalogSince` + `CatalogSinceAge` + the
|
|
tolerance WARN), `controller/internal/web/funcmap.go`, `controller/internal/web/templates/app_info.html`,
|
|
`controller/internal/web/templates/stacks.html`, plus `CHANGELOG.md`, `CONTEXT.md`, `REUSE.md`,
|
|
`controller/README.md`.
|
|
|
|
## 3. Commits pushed to `main`
|
|
|
|
| repo | commit | what |
|
|
|---|---|---|
|
|
| felhom-controller | **`8025304acc0a6737`** | v0.233.0 — the record and the badge |
|
|
| app-catalog-felhom.eu | `69761cf91bfc` | `catalog_since` on all 53 apps + the `CLAUDE.md` rule |
|
|
| app-catalog-felhom.eu | `8220f8dc…` | the catalog's own REPORT |
|
|
| felhom.eu | `6035dfcc3ae1` | `09-update-architecture.md`, the register, roadmap, capability map, STATUS, live evidence |
|
|
| felhom.eu | `e86cf42e0ba5` | R-454..R-456, filed because the observations gate refused a report that filed none |
|
|
|
|
No branches. All three gate runs passed on push (controller: 13 gates OK + 1 advisory, see §9).
|
|
|
|
## 4. Tests, and both companion red-proofs
|
|
|
|
**1707 → 1724 test functions (+17). 28 packages, 0 FAIL**, `go build ./... && go vet ./... && go test ./...`.
|
|
|
|
| group | what it pins |
|
|
|---|---|
|
|
| **A** | a MULTI-service fixture records one entry PER COMPOSE SERVICE with digests and a parseable RFC3339 `at`; an image with no `RepoDigests` is recorded with an EMPTY digest, never skipped; a partial read is recorded AND logged with the count and the missing service names |
|
|
| **B** | the record follows the CONTAINER, not the file — the fixture's compose says `v2.8.5` while the container runs `v2.8.6` and the record carries `v2.8.6`; an unchanged observation does NOT rewrite `app.yaml` and `at` is carried forward |
|
|
| **C** | an unwritable `app.yaml` does not fail the action — driven through a real `RestartStack` |
|
|
| **D** | the badge's states incl. no-record-renders-nothing, no version string ever, and nothing badged that cannot be judged (undeployed / protected / orphaned); plus a RENDER of both production templates |
|
|
| **E** | **the wiring** — `RestartStack` reached end to end, plus an **AST walk** of the four call sites |
|
|
| **F** | `catalog_since` tolerance: absent, blank, `tegnap`, `18/07/2026`, `2026-13-45`, and a FUTURE date |
|
|
|
|
**Three companion red-proofs, each run, observed failing, and reverted (2026-09-02):**
|
|
|
|
| # | mutation | observed failure |
|
|
|---|---|---|
|
|
| 1 | give `recordInstalledImages` an `error` return and make `RestartStack` return it | `TestGroupC` fails: *"restart must SUCCEED even when the record cannot be written: … permission denied"* — the customer's app refuses to start because a note could not be written |
|
|
| 2 | the no-record guard returns `updateCurrent` instead of `updateUnknown` | three sub-tests fail, incl. `badge = &{Label:Naprakész …}, want present=false` on an app nobody has ever measured |
|
|
| 3 | delete the `{{template "meta_badge" (updateBadge …)}}` line from each template in turn | `stacks.html` → *"the behind badge is missing from stacks"*; `app_info.html` → the same on `app_info` |
|
|
|
|
All three reverted; the full suite re-run green afterwards.
|
|
|
|
**Seam discipline.** The recorder has its own seam (`installedExecFn`) that FAILS the test on an argv
|
|
it does not recognise. The wiring test does **not** use it to reach the recorder: it stubs the compose
|
|
binary on `PATH` and drives the real `RestartStack`. The AST walk exists because a
|
|
`strings.Contains` matches a commented-out call, which is exactly this project's
|
|
seam-built-but-never-wired class.
|
|
|
|
## 5. Deployed version
|
|
|
|
```
|
|
$ ssh hp "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'"
|
|
gitea.dooplex.hu/admin/felhom-controller:0.233.0 Up 19 seconds (healthy)
|
|
```
|
|
|
|
Image digest `sha256:df5940ccf5a548ceee9065a2cc55a467f8941c636138441e0d77e320dc2023ab`. Previous: `0.232.0`.
|
|
|
|
**The build was blocked and the workaround is recorded rather than buried.** Docker Hub returned
|
|
`429 toomanyrequests` for `debian:bookworm-slim` and `golang:1.24-bookworm`, so `build.sh` could not
|
|
resolve either base image; DooPlex holds no Docker Hub login. Both were pulled from **Google's
|
|
official Docker Hub mirror** and retagged locally, after which `build.sh` ran unmodified:
|
|
|
|
```
|
|
mirror.gcr.io/library/debian:bookworm-slim sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171
|
|
mirror.gcr.io/library/golang:1.24-bookworm sha256:1a6d4452c65dea36aac2e2d606b01b4a029ec90cc1ae53890540ce6173ea77ac
|
|
```
|
|
|
|
**GRADED HONESTLY: that these are byte-identical to Docker Hub's is KNOWN, not MEASURED here** — the
|
|
throttle was still in force at the end of the session, so `docker manifest inspect` could not be run
|
|
against Hub for the comparison. The digests are recorded above precisely so the check is one command
|
|
whenever the window clears. No host configuration was changed and no daemon was restarted.
|
|
|
|
## 6. Live validation — what was proven, and by which method
|
|
|
|
**Full evidence: `felhom.eu/documentation/tests/VALIDATION-update-slice12-2026-09-02.md`.**
|
|
|
|
**Method, stated: the BOOT RECONCILER** — `bootrecon.Run → StartStack → compose up -d →
|
|
recordInstalledImages`, a real production caller, the same one the spike used, with no hand-set state.
|
|
**Not the customer's Restart button, and §7 is why.**
|
|
|
|
Ground truth was read from the containers **before anything was touched**, independently:
|
|
|
|
```
|
|
bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 @sha256:eaeea1e447205a79cb61d7efdc6966f37311dc1bc9c36a3a5c897bf79107c2c3
|
|
bookstack lscr.io/linuxserver/bookstack:26.05.2 @sha256:3db259db582808ab498d49ae96b0a63f935d9cf3635c9d5bd8b8815c6ff1f8a1
|
|
bookstack-db mariadb:12.3 @sha256:a02fe89cb597d4375812b2eac90cf9d0775d4686daa7f7cc750ebbcad7525bbc
|
|
```
|
|
|
|
Before: **nine deployed apps, ZERO with a record.** After, the multi-service case — the one that
|
|
matters, because one entry for the whole stack is the wrong outcome:
|
|
|
|
```
|
|
18:28:48 bootrecon.go:259: [INFO] Boot reconciliation: 1 boot-orphaned app(s) found: [bookstack]
|
|
18:28:54 installed.go:408: [INFO] [stacks] installed-images bookstack: recorded 2 service(s)
|
|
(bookstack=lscr.io/linuxserver/bookstack:26.05.2 (sha256:3db259db5828…),
|
|
bookstack-db=mariadb:12.3 (sha256:a02fe89cb597…))
|
|
```
|
|
|
|
```yaml
|
|
installed_images:
|
|
bookstack:
|
|
ref: lscr.io/linuxserver/bookstack:26.05.2
|
|
digest: sha256:3db259db582808ab498d49ae96b0a63f935d9cf3635c9d5bd8b8815c6ff1f8a1
|
|
at: "2026-09-02T18:28:54Z"
|
|
bookstack-db:
|
|
ref: mariadb:12.3
|
|
digest: sha256:a02fe89cb597d4375812b2eac90cf9d0775d4686daa7f7cc750ebbcad7525bbc
|
|
at: "2026-09-02T18:28:54Z"
|
|
```
|
|
|
|
**All three recorded digests match the independently-read ground truth exactly.** `bookstack`'s two
|
|
`ENC:` secrets are **byte-identical** before and after, as are `deployed`, `deployed_at`,
|
|
`locked_fields` and `desired_state`. `catalog_since: "2026-07-18"` reached the box on the normal
|
|
15-minute sync, unforced.
|
|
|
|
**Hungarian strings, in the DEPLOYED binary, ASCII fragments with both controls** — a positive
|
|
observable on the shipped artifact, and **not** a rendered page:
|
|
|
|
| fragment | count | |
|
|
|---|---|---|
|
|
| `Naprak` | **3** | positive |
|
|
| `Friss` | **25** | positive |
|
|
| `napja` | **2** | positive |
|
|
| `legfrissebb` | **1** | positive (the hover text) |
|
|
| `zzz-never-present-control` | **0** | negative control |
|
|
| `Nem-karbantartott-XYZ` | **0** | negative control |
|
|
|
|
**End state:** all three containers back on the same images, both named volumes untouched, both apps
|
|
healthy. **This run provisioned nothing** — no guest, no storage, no hub record — so there is no
|
|
teardown to report on any of the three layers.
|
|
|
|
## 7. NOT yet live-validated — an explicit list
|
|
|
|
1. **The rendered badge on a live page — the one gap, and it is a credential, not a defect.**
|
|
Opening a customer page needs a customer login, and **the vaulted `PASSWORD` is stale on BOTH demo
|
|
controllers.** Attempts, in full:
|
|
- `POST /login` to demo-hp guest `https://192.168.0.138:443`, `Host: felhom.enkisfelhom.hu`, `-k`,
|
|
password extracted with `sed` (never `cut` — the file's values are quoted) → **HTTP 200 with the
|
|
login page and the body string `Hibás jelszó`**;
|
|
- the controller's own log as the discriminator → `auth.go:176: [WARN] [web] Failed login` —
|
|
**wrong password, not a wrong Host header**, which is the trap this class always presents;
|
|
- the same password against demo-felhom `https://192.168.0.149:443`,
|
|
`Host: felhom.demo-felhom.eu` → **also `Hibás jelszó`**;
|
|
- every other key in `~/.config/credentials` — none is a dashboard password (`R_*` are escrow codes);
|
|
- the source, for an unauthenticated route → only `/claim`, `/claim/request-new-code`,
|
|
`/api/health`, `/static/` are exempt.
|
|
|
|
Filed as **R-453 (WAITING-ON-OPERATOR)** and raised in `STATUS.md` item 9 with two options.
|
|
**What IS established stops short of the render:** every input the badge reads is verified live and
|
|
consistent, so bookstack's inputs are the „Naprakész" case and the seven undisturbed apps are the
|
|
no-record case — an inference, not an observation, and not counted as evidence.
|
|
2. **`POST /api/stacks/{name}/deploy` and `/restart`** — same cause. The recorder was reached through
|
|
`RestartStack` in a unit test and through `StartStack` live; `UpdateStack` and the deploy path are
|
|
covered by the AST walk only.
|
|
3. **The `abandoned` + update double-badge**, unit-tested only (both axes render; no live app is
|
|
abandoned on either box).
|
|
4. **Any behaviour on a box other than demo-hp.** demo-felhom is still on 0.232.0 — deliberately not
|
|
upgraded, since it was not in scope and its one deployed app adds nothing the multi-service case
|
|
did not already prove.
|
|
|
|
## 8. Register — 194 rows before, 205 after. Nothing closed.
|
|
|
|
R-438 and R-440 **amended and both stay OPEN** — the mechanism is documented, not changed — so
|
|
`CLOSED-ITEMS.md` is untouched, and this run's compression sweep is a no-op that says so.
|
|
|
|
New: **R-446** floating-tag honesty (P2, CC) · **R-447** slice 3, **BLOCKED** on an operator ruling
|
|
(P1) · **R-448** slice 4 (P2) · **R-449** slice 5 (P2) · **R-450** slice 6 (P2) · **R-451** slice 7
|
|
(P3) · **R-452** the `catalog_since` gate, deferred because the runner fetches at `--depth 1` (P3) ·
|
|
**R-453** the stale dashboard password (P2, WAITING-ON-OPERATOR) · **R-454** five `gofmt`-unclean test files with no gate (P3) · **R-455** DooPlex has no Docker Hub login and the ceiling now blocks builds (P2, WAITING-ON-OPERATOR) · **R-456** a partly-dead stack is not a boot orphan and that is written down nowhere (P3).
|
|
|
|
## 9. Observations — noticed, documented, NOT acted on
|
|
|
|
1. **The golden is one release behind.** The push gate advises: newest released controller `0.233.0`,
|
|
newest golden baked `0.232.0`. Baking and vouching a golden is a three-field change and was not in
|
|
this task's scope. **NOT-A-FINDING: the `golden-notice` gate raises this on every release and `STATUS.md` already carries the debt — a register row would duplicate an instrument that already fires by itself.**
|
|
2. **Five test files in `internal/web/` are not `gofmt`-clean at the baseline** — `backups_split_test.go`,
|
|
`claim_code_naming_test.go`, `disk_health_test.go`, `r400_debug_routes_test.go`, `recovery_test.go`.
|
|
Pre-existing; both files I added are clean. Not reformatted, under the minimal-changes rule. **FILED: R-454.**
|
|
3. **The catalog's non-static gates could not be run to a verdict on this host.** `image-pins` **OK**;
|
|
`image-resolvable` INCONCLUSIVE (Docker Hub throttled 6 of 65 unauthenticated lookups — the same
|
|
ceiling that blocked the build); `volume-persistence` INCONCLUSIVE (its own canary needs a scratch
|
|
Docker host). Neither is caused by the change, and the runner exited 0. **FILED: R-455** — the same ceiling blocked the BUILD (§5), which is a bigger bill than a gate that cannot reach a verdict.
|
|
4. **`bentopdf`'s `catalog_since` needed a decision, not just a script.** The two spike commits that
|
|
moved its pin and reverted it the same hour were **excluded by hash**; counting them would have
|
|
dated it 2026-09-02 for a pin unchanged since 2026-07-12. The catalog's own CHANGELOG already calls
|
|
them a measurement, not a release. **NOT-A-FINDING: the decision and its reason are already recorded durably in `app-catalog-felhom.eu`'s `CHANGELOG.md` and `REPORT.md`; it is a settled call, not an open question.**
|
|
5. **The boot reconciler does not treat a partly-dead stack as a boot orphan.** Removing only
|
|
`bookstack`'s app container while its DB stayed up left it unselected; removing both made it an
|
|
orphan. Correct-looking behaviour, recorded because it cost a second pass and is not written down
|
|
anywhere. **FILED: R-456.**
|
|
|
|
## 10. Every claim in the task that turned out to be wrong, named
|
|
|
|
1. **The source paths omit the module directory** — everything is under `controller/`. **All twelve
|
|
line-number landmarks were exact**, so this is a prefix, not drift.
|
|
2. **`app_info.html` ~L48 and `stacks.html` ~L89 point at neighbouring places, not at the badge.**
|
|
L48 opens `stack-meta-badges` (a different badge row) and L89 is the `Frissítés` button. The
|
|
`meta_badge` calls the new badge had to join are at **L13** and **L42**. Both were found by reading,
|
|
as instructed.
|
|
3. **"No STOP in this task ... nothing is waiting on the operator."** True of the change; **false of
|
|
verifying it.** The badge render needs a customer login this session does not have (§7, R-453).
|
|
4. **Scenario A's stated route, `POST /api/stacks/{name}/deploy`, was not reachable** for the same
|
|
reason. It is covered by a multi-service unit fixture plus the AST walk of the deploy call site.
|
|
5. **"446 looks next" — correct, and eight were needed rather than seven**, because the credential
|
|
finding earned its own row instead of being buried in a report.
|
|
6. Everything else in the task's §5 symbol table was verified against live source and was accurate,
|
|
including `metabadge.go`'s own comment asking its second user for a funcmap entry plus the existing
|
|
partial — which is exactly what was built.
|