the badge IS proven live, and the 'stale password' finding was mine, not the box's
gates / gates (push) Successful in 16s

I reported that the vaulted dashboard password no longer worked on either demo
box, and quoted the controller's own 'Failed login' as the discriminator. The
password was fine. ~/.config/credentials quotes its values with SINGLE quotes and
my sed stripped only double quotes, so the quote characters went out as part of
the password. The operator corrected it in one line; one retry returned 302.

The instrumentation lesson is the finding and R-453 now carries it: 'Failed login'
separates wrong-password from wrong-Host-header, and that is ALL it separates. It
cannot tell a wrong password from wrong password HANDLING, and I read it as if it
could. This is the second time this file's quoting has produced a confident wrong
verdict, so the fix is one shared extraction helper, not a resolution to be careful.

With the session recovered, the badge is validated on live pages: Naprakesz twice
on /stacks and on /apps/bookstack; NO badge at all on /apps/docmost (a deployed app
with no record - absent is UNKNOWN, not current); and 'Frissites elerheto - 52
napja' on both surfaces, the age being real arithmetic on bentopdf's catalog_since.
The behind state was staged by editing one compose tag, with no restart and no
up -d, and reverted byte-identically (sha256 equal, diff empty, container never
touched). Capability-map row upgraded to PROVEN-LIVE with the one unexercised
badge state named. STATUS item 9 now needs nothing from the operator.
This commit is contained in:
2026-09-02 20:47:53 +02:00
parent e86cf42e0b
commit 0705942783
5 changed files with 116 additions and 63 deletions
@@ -7,8 +7,8 @@ at the end of the phase that produced it, not at the end of the session.
**Which path was used, stated exactly: the BOOT RECONCILER**, `bootrecon.Run → StackProvider.StartStack
→ compose up -d → recordInstalledImages` — a REAL production caller, the same one
`SPIKE-app-update-2026-09-01` §2 variant 1c-ii used, and no hand-set state anywhere.
**Why not the customer's Restart button: see §4 — the vaulted dashboard password no longer opens
either demo controller.**
**Why not the customer's Restart button: §4.0 — the dashboard login was lost to a mistake of mine
for part of the run, and the record half was measured before it was recovered.**
---
@@ -123,38 +123,97 @@ $ grep -n catalog_since /opt/docker/stacks/bookstack/.felhom.yml
13:catalog_since: "2026-07-18"
```
## 4. The badge render — **NOT LIVE-VALIDATED. What was tried, in full.**
## 4. The badge render — **PROVEN LIVE on both surfaces, three of the four states**
**A "no access" claim must list its attempts.** These are the attempts:
### 4.0 A false diagnosis of my own, corrected here rather than buried
| # | attempt | result |
|---|---|---|
| 1 | `POST /login` to demo-hp guest `https://192.168.0.138:443`, `Host: felhom.enkisfelhom.hu`, `-k`, password from DooPlex `~/.config/credentials` `PASSWORD` (extracted with `sed`, never `cut` — the values are quoted) | **HTTP 200 with the login page and the body string `Hibás jelszó`** |
| 2 | the controller's own log, as the discriminator between "wrong host header" and "wrong password" | `auth.go:176: [WARN] [web] Failed login from 172.18.0.3` — **wrong password, not a routing problem** |
| 3 | the same password against the OTHER demo box, demo-felhom guest `https://192.168.0.149:443`, `Host: felhom.demo-felhom.eu` | **HTTP 200 + `Hibás jelszó` as well** |
| 4 | every other key in `~/.config/credentials` — `HUB_PW`, `R_DEMO-HP`, `R_DEMO-FELHOM`, `R_C11_REWALK`, `R_PART4`, `TS_KEY`, `HETZNER_API`, `ISO_S3_*` | none is a dashboard password; the `R_*` keys are escrow recovery codes |
| 5 | reading the source for an unauthenticated route to an app page | only `/claim`, `/claim/request-new-code`, `/api/health` and `/static/` are exempt (`internal/web/auth.go`) |
The first pass reported "the vaulted dashboard password is stale on both demo boxes", with the
controller's own `[WARN] Failed login` quoted as the discriminator. **The password was fine. The
extraction was wrong.** `~/.config/credentials` quotes its values with **single** quotes and the `sed`
used stripped only double quotes, so the literal `'` characters were sent as part of the password.
The operator said so, one retry with `sed "s/^['\"]//;s/['\"]$//"` returned **HTTP 302 +
`felhom_session`**, and everything below followed.
**So the vaulted `PASSWORD` is stale on BOTH demo controllers.** It was already recorded as having
drifted once (memory `demo-hp-guest-controller-access`, 2026-08-09, put back on operator instruction);
demo-hp was reinstalled and re-claimed on 2026-08-21, and demo-felhom has now drifted too.
**This is exactly the trap the `credentials-file-values-are-quoted` memory records** — it was applied
half-way. The controller's log was a true observation and a misleading one: `Failed login` proves the
BYTES did not match the hash; it says nothing about whose fault that is. **A discriminator that
separates "wrong password" from "wrong host header" does not separate "wrong password" from "wrong
password handling", and I read it as if it did.** Register row R-453 was opened on the wrong premise
and is corrected there.
**There is no operator-side route to a customer's dashboard password** — the claim code is
bcrypt-hashed hub-side and only emailed (R-119). Re-setting it means writing a new bcrypt hash into the
guest's `data/settings.json`, which is a decision about a customer account and was done under operator
instruction last time. **It is therefore a HUMAN step, by this project's own rule**, and it is not
taken here.
### 4.1 „Naprakész" — quoted from the live pages
**What IS established about the badge, and it stops short of the render:** every INPUT the badge reads
is verified live and consistent on this box — `installed_images` present with refs matching the
template's pins exactly (§2.2 vs the compose file's `lscr.io/linuxserver/bookstack:26.05.2` and
`mariadb:12.3`), and `catalog_since: "2026-07-18"` present (§3). So bookstack's inputs are the
`Naprakész` case and the seven undisturbed apps are the no-record case. **That is an inference from
verified inputs, not an observation of the rendered page, and it is not counted as evidence.**
`GET /apps/bookstack` and `GET /stacks`, session-authenticated, `Host: felhom.enkisfelhom.hu`:
The render itself is covered by unit tests that render the PRODUCTION templates
(`TestGroupD_BadgeRendersOnBothSurfaces`, both surfaces, all states, with a companion red-proof), which
is the strongest statement available without the password.
```html
<span class="tag tag-ok" title="Ez az alkalmazás a legfrissebb elérhető változatot futtatja.">Naprakész</span>
```
Byte-identical to the string table in the task. **On `/stacks` it appears exactly TWICE** — the two
apps that have a record — while the **seven other deployed apps render NOTHING**. That is the
no-record case, observed live and not inferred.
### 4.2 „Frissítés elérhető — N napja" — the age comes from `catalog_since`, live
To reach the behind state the badge needs the template to pin something the container is not running.
Staged on **`bentopdf`** — the app with no database, no volume and no data of any kind — by editing
**only** its `docker-compose.yml` tag `v2.8.6 → v2.8.5` and touching nothing else. **The container was
never restarted and no `up -d` ran**; the file is the badge's input, and this is the same shape the
syncer produces on its own. `ScanStacks` (2-minute cadence) picked it up:
```html
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.">Frissítés elérhető — 52 napja</span>
```
**52 napja is arithmetic on a real catalog value, not a placeholder:** bentopdf's `catalog_since` is
`2026-07-12` and the run is 2026-09-02 — 52 days. It rendered on the app page **and** the app list.
**REVERTED IMMEDIATELY, and verified byte-identical:** `sha256` before and after both
`39679e28cdd6ee8f46e359290b4d631cf234a1cfdae374cd84c8fe7588870ed1`, `diff` empty, container still
`ghcr.io/alam00000/bentopdf:v2.8.6 Up 17 minutes (healthy)` throughout. The badge then returned to
„Naprakész" with `napja` count **0**.
**GRADED HONESTLY: the compose file was placed by hand, not by the syncer.** What is proven is the
render — compose file → `ScanStacks` → `TemplateImages` → `updateBadge` → page — with the file
carrying exactly what a sync would have written. The syncer's own half is separately measured in
`SPIKE-app-update-2026-09-01` §3.
### 4.3 ASCII-fragment counts, with a positive and a negative control
Accented text is never grepped directly here. `grep -oF`, so no `.` is a wildcard — the correction the
spike had to make on itself.
| fragment | `/stacks` (current) | `/apps/bookstack` | `/apps/docmost` (legacy) | `/stacks` (behind) | `/apps/bentopdf` (behind) |
|---|---|---|---|---|---|
| `Naprak` | **2** | **1** | **0** | 1 | 0 |
| `napja` | 0 | 0 | 0 | **1** | **1** |
| `52 napja` | 0 | 0 | 0 | **1** | **1** |
| `legfrissebb` (hover) | 2 | 1 | 0 | — | — |
| `BookStack` (positive control) | 1 | 4 | 0 | — | — |
| `Docmost` (positive control) | 1 | 0 | 5 | — | — |
| `zzz-never-present` (**negative control**) | **0** | **0** | **0** | **0** | **0** |
| `Nem-karbantartott-XYZ` (**negative control**) | **0** | **0** | **0** | — | — |
`/apps/docmost` is the load-bearing column: a deployed app with no record renders **no badge at all**,
live. **Absent is UNKNOWN, and it is not rendered as current.**
### 4.4 Nothing about updating changed — checked on the live page
In the behind state, `/stacks` still carries exactly one of each for bentopdf:
```
stackAction(event, 'bentopdf', 'update') ×1
stackAction(event, 'bentopdf', 'restart') ×1
stackAction(event, 'bentopdf', 'stop') ×1
```
The badge is wired to nothing.
### 4.5 The one state NOT reachable live
**„Frissítés elérhető" with no age** — it needs an app whose `catalog_since` is absent, malformed or
future-dated, and all 53 catalog apps now carry a valid one. Covered by `TestGroupF`, which walks
absent, blank, `tegnap`, `18/07/2026`, `2026-13-45` and a future date.
## 5. End state — nothing left broken, nothing provisioned