6035dfcc3a
gates / gates (push) Successful in 17s
R-438's document half. It records how an update works AS MEASURED, quotes the RestartStack comment that proves the restart half was CHOSEN (a design decision is not a defect), carries the three operator rulings of 2026-09-02, strikes the word 'rollback' (once a migration has run the old image will not start), states the target shape, and lists the seven slices with a status each. R-438 and R-440 amended and BOTH STAY OPEN: the mechanism is documented, not changed. Nothing closed, so CLOSED-ITEMS.md is untouched. Eight new register rows, 194 -> 202: R-446 (Naprakesz can be false for the 23 floating pins), R-447..R-451 (one per remaining slice, with a rank and an owner), R-452 (no gate enforces catalog_since - the runner fetches at --depth 1), and R-453 (the vaulted dashboard password is stale on BOTH demo boxes, which is what stopped the badge render from being validated live). Live evidence for slices 1 and 2 in documentation/tests/. The record is PROVEN LIVE through the boot reconciler on demo-hp - one entry per compose service, digests matching ground truth read independently. The badge RENDER is not, and the five attempts are listed rather than summarised.
171 lines
9.2 KiB
Markdown
171 lines
9.2 KiB
Markdown
# VALIDATION — update arc slices 1 & 2, live on demo-hp (2026-09-02)
|
|
|
|
**Controller `0.233.0`, guest 9201 on `demo-hp` (Tier 0 — disposable).** Evidence copied off the box
|
|
at the end of the phase that produced it, not at the end of the session.
|
|
|
|
**Method: endpoint/production-path level.** `claude-in-chrome` is not available on DooPlex.
|
|
**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.**
|
|
|
|
---
|
|
|
|
## 1. Deployed version
|
|
|
|
```
|
|
$ 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)
|
|
```
|
|
|
|
Previous: `0.232.0`. Image digest `sha256:df5940ccf5a548ceee9065a2cc55a467f8941c636138441e0d77e320dc2023ab`.
|
|
|
|
## 2. The record — PROVEN LIVE, on a single-service AND a multi-service app
|
|
|
|
### 2.0 Before — nine deployed apps, ZERO with a record
|
|
|
|
```
|
|
bentopdf deployed=1 installed_images=0 paperless-ngx deployed=1 installed_images=0
|
|
bookstack deployed=1 installed_images=0 privatebin deployed=1 installed_images=0
|
|
calibre-web deployed=1 installed_images=0 romm deployed=1 installed_images=0
|
|
docmost deployed=1 installed_images=0
|
|
kimai deployed=1 installed_images=0
|
|
opengist deployed=1 installed_images=0
|
|
```
|
|
|
|
**That is the legacy case, and it is the state the badge must render NOTHING for.**
|
|
|
|
### 2.0b Ground truth, read from the containers BEFORE anything was touched
|
|
|
|
```
|
|
bentopdf ref=ghcr.io/alam00000/bentopdf:v2.8.6 rd=…@sha256:eaeea1e447205a79cb61d7efdc6966f37311dc1bc9c36a3a5c897bf79107c2c3
|
|
bookstack ref=lscr.io/linuxserver/bookstack:26.05.2 rd=…@sha256:3db259db582808ab498d49ae96b0a63f935d9cf3635c9d5bd8b8815c6ff1f8a1
|
|
bookstack-db ref=mariadb:12.3 rd=…@sha256:a02fe89cb597d4375812b2eac90cf9d0775d4686daa7f7cc750ebbcad7525bbc
|
|
```
|
|
|
|
**This is the discriminator.** Every digest below is compared against these, taken independently.
|
|
|
|
### 2.1 Single service — `bentopdf`
|
|
|
|
Staged exactly as the spike stages a boot orphan: `docker rm -f bentopdf` (chosen because it has **no
|
|
database, no volume and no data of any kind**), then the controller restarted so the reconciler runs.
|
|
`desired_state: running` was left untouched. **Nothing else was staged; the reconciler selected the app
|
|
on its own.**
|
|
|
|
```
|
|
18:27:32 main.go:2162: [INFO] [bootrecon] boot window: fleet settled after 30s — sweeping
|
|
18:27:32 bootrecon.go:259: [INFO] [bootrecon] Boot reconciliation: 1 boot-orphaned app(s) found: [bentopdf]
|
|
18:27:32 manager.go:1066: [INFO] [stacks] Starting stack: bentopdf
|
|
18:27:32 manager.go:1081: [INFO] [stacks] Stack bentopdf started successfully (took 0.3s)
|
|
18:27:32 installed.go:408: [INFO] [stacks] installed-images bentopdf: recorded 1 service(s) (bentopdf=ghcr.io/alam00000/bentopdf:v2.8.6 (sha256:eaeea1e44720…))
|
|
```
|
|
|
|
`/opt/docker/stacks/bentopdf/app.yaml` after:
|
|
|
|
```yaml
|
|
installed_images:
|
|
bentopdf:
|
|
ref: ghcr.io/alam00000/bentopdf:v2.8.6
|
|
digest: sha256:eaeea1e447205a79cb61d7efdc6966f37311dc1bc9c36a3a5c897bf79107c2c3
|
|
at: "2026-09-02T18:27:32Z"
|
|
```
|
|
|
|
**Digest MATCHES §2.0b exactly.**
|
|
|
|
### 2.2 Multi-service — `bookstack`. **The case that matters.**
|
|
|
|
**One entry for the whole stack is the wrong outcome, and only a multi-container app can tell.**
|
|
|
|
Same staging (`docker rm -f bookstack bookstack-db`; both volumes are NAMED —
|
|
`bookstack_bookstack_config`, `bookstack_bookstack_db_data` — and were verified present, so no data
|
|
was at risk), then the controller restarted.
|
|
|
|
```
|
|
18:28:48 bootrecon.go:259: [INFO] [bootrecon] Boot reconciliation: 1 boot-orphaned app(s) found: [bookstack]
|
|
18:28:48 manager.go:1066: [INFO] [stacks] Starting stack: 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…))
|
|
18:28:54 bootrecon.go:303: [INFO] [bootrecon] Boot reconciliation complete: 1 app(s) recovered in 1 attempt(s)
|
|
```
|
|
|
|
```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"
|
|
```
|
|
|
|
**TWO entries, keyed by COMPOSE SERVICE NAME, both digests matching §2.0b exactly.**
|
|
|
|
**A NOTE ON WHY IT TOOK TWO PASSES, because it is a real fact about the reconciler and not a mishap.**
|
|
The first pass removed only the `bookstack` app container and left `bookstack-db` running; the
|
|
reconciler did **not** select bookstack — it found `[bentopdf]` alone. A stack with one live member is
|
|
not a boot orphan to it. Removing the DB container as well made the whole stack orphaned and it was
|
|
selected on the next pass. Both apps are running and healthy at the end (§5).
|
|
|
|
### 2.3 The encrypted secrets survived the write — checked, not assumed
|
|
|
|
`bookstack`'s `app.yaml` holds two encrypted values. Before and after the record was written, both
|
|
`ENC:` strings are **byte-identical**, and `deployed`, `deployed_at`, `locked_fields` and
|
|
`desired_state` are unchanged. This is the copy-and-overlay property (R-100) holding under a new field.
|
|
|
|
## 3. `catalog_since` reached the box by the NORMAL 15-minute sync
|
|
|
|
No force, no hand-edit:
|
|
|
|
```
|
|
$ 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.**
|
|
|
|
**A "no access" claim must list its attempts.** These are the attempts:
|
|
|
|
| # | 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`) |
|
|
|
|
**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.
|
|
|
|
**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.
|
|
|
|
**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.**
|
|
|
|
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.
|
|
|
|
## 5. End state — nothing left broken, nothing provisioned
|
|
|
|
```
|
|
$ pct exec 9201 -- docker ps --format '{{.Names}}|{{.Image}}|{{.Status}}' | grep -E 'bookstack|bentopdf'
|
|
bookstack|lscr.io/linuxserver/bookstack:26.05.2|Up 10 seconds (health: starting)
|
|
bookstack-db|mariadb:12.3|Up 16 seconds (healthy)
|
|
bentopdf|ghcr.io/alam00000/bentopdf:v2.8.6|Up About a minute (healthy)
|
|
```
|
|
|
|
All three containers are back on the SAME images they ran before, both named volumes untouched, and
|
|
both apps carry a complete record. **This run provisioned nothing** — no guest, no storage, no hub
|
|
record — so there is no teardown to report on any of the three layers.
|