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.
16 KiB
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.goand so on. The Go module lives undercontroller/— every one of those iscontroller/internal/.... Every line number in the task was EXACT against the baseline (AppConfig99,runComposeDeploy395,LoadAppConfig792,SaveAppConfig806,SensitiveEnvVars889,stackEnv1233,composeExecCustomEnv1268,execCommand1344,logPostStartStatus1382,checkLocalImages1410,Metadata14,isOperationalState90) — 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…))
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
-
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
PASSWORDis stale on BOTH demo controllers. Attempts, in full:POST /loginto demo-hp guesthttps://192.168.0.138:443,Host: felhom.enkisfelhom.hu,-k, password extracted withsed(nevercut— the file's values are quoted) → HTTP 200 with the login page and the body stringHibá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→ alsoHibá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.mditem 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. -
POST /api/stacks/{name}/deployand/restart— same cause. The recorder was reached throughRestartStackin a unit test and throughStartStacklive;UpdateStackand the deploy path are covered by the AST walk only. -
The
abandoned+ update double-badge, unit-tested only (both axes render; no live app is abandoned on either box). -
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
- The golden is one release behind. The push gate advises: newest released controller
0.233.0, newest golden baked0.232.0. Baking and vouching a golden is a three-field change and was not in this task's scope. NOT-A-FINDING: thegolden-noticegate raises this on every release andSTATUS.mdalready carries the debt — a register row would duplicate an instrument that already fires by itself. - Five test files in
internal/web/are notgofmt-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. - The catalog's non-static gates could not be run to a verdict on this host.
image-pinsOK;image-resolvableINCONCLUSIVE (Docker Hub throttled 6 of 65 unauthenticated lookups — the same ceiling that blocked the build);volume-persistenceINCONCLUSIVE (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. bentopdf'scatalog_sinceneeded 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 inapp-catalog-felhom.eu'sCHANGELOG.mdandREPORT.md; it is a settled call, not an open question.- 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
- 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. app_info.html~L48 andstacks.html~L89 point at neighbouring places, not at the badge. L48 opensstack-meta-badges(a different badge row) and L89 is theFrissítésbutton. Themeta_badgecalls the new badge had to join are at L13 and L42. Both were found by reading, as instructed.- "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).
- 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. - "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.
- 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.