burndown 3: report, evidence, register (174 -> 168), 07 Redis RDB [DESIGN], 04 §6.3 the box's password doors
gates / gates (push) Successful in 7m2s

Closed R-1115, R-1111, R-1112, R-1113, R-1109, R-1110, R-1114, R-1096; opened R-1116 (decoy suites in the other
runners), R-1117 (Immich/Nextcloud Redis volume, deferred). Controller 0.325.0 is the floor since 08:21 UTC.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-17 10:28:03 +02:00
parent 0314b8e7cf
commit e1dd1cadd3
40 changed files with 2654 additions and 14 deletions
+160
View File
@@ -0,0 +1,160 @@
# REPORT — burndown 3 (2026-10-17): the translation gate's lost case, two limiters, family sessions, three small fixes, Redis
Written in ASD-STE100 Simplified Technical English. Brief: „burndown 3" (2026-10-17). Design read first:
`architecture/04-control-plane-authorization.md` (it had no section on the box's own password doors — added as §6.3),
`07-backup-architecture.md` (app classes, the nightly volume dump), `09-update-architecture.md` (ladder format L2257–2263,
decision 4 L156, part 5 L2325, „frozen WHOLE" L2662). Evidence: `documentation/audits/burndown-3-2026-10-17/`.
## Plain words first
- Everything in the brief is done. No STOP fired. Nothing waits on you.
- Controller 0.325.0 is the floor for every box since 08:21 UTC, after Tester 1, demo-hp and demo-felhom updated by
themselves and passed the box walk.
- Five apps' Redis now saves snapshots. It was proven first on the scratch box, then switched on demo-hp. Every other box
switches at the app's next start, normally the same night's backup. Tester 1 does not get it yet: it still follows the
drill catalog (R-1022, waits on your word).
- Two findings came from live tests and were fixed before the release: the family list was converted only on the first
family visit (now at start), and the off-site wizard said „Hibás jelszó." to English boxes too.
- One mistake of mine, fixed: my Samba test tool wrote the throwaway test-share password into its log. I removed it from
the evidence, replaced the password before turning sharing off, and changed the tool to redact it.
## 1. Each part
| Part | Done | Note |
|---|---|---|
| 1 R-1115 | yes | the decoy moved; fixed; the decoy suite is now a gate (full run) |
| 2 R-1111 | yes | one limiter body for three doors, parallel tests red-proved |
| 3 R-1112, R-1113 | yes | fingerprints + conversion at start (found live); one household session |
| 4 R-1109, R-1110, R-1114 | yes | samba label; guard over 42 calls; one resolver for the sibling |
| 5 release | yes | 0.325.0, per-box floors, box walk, global floor, read back |
| 6 R-1096 | yes | 6a proof passed; 6b reading confirmed; 6c pushed and checked |
## 2. R-1115 — what moved
- **The decoy moved, not the gate.** Commit `326f846` (R-1102, 2026-10-16) removed gokapi's Hungarian login line. The
case appended an English login to gokapi, so the gate had nothing to compare and rightly said OK (rc 0).
- **Fixed side: the decoy.** It now rewrites wger's existing English login in place (when the line is gone, the suite
stops, so it cannot pass on a lost premise again). New case: calibre-web `admin123` → `admin1234`. Both: rc 1, token named.
- **Gate run time:** the decoy suite takes 196 s (measured twice). So it joined `catalog_gates.py`'s full run as
`gate-decoys`, not `--fast`.
- **Also found:** `test_catalog_gates.py` was red on main since the night before (it counted 18 gates; 19 were registered —
my miss last night). Fixed; now 21 with today's two.
- **Whole catalog after the fix:** the copy gate says OK. No real template convicted.
## 3. R-1110 — each call site and its verdict (42 calls, `P2-P4/r1110-census.txt`)
| Calls | Where | Verdict |
|---|---|---|
| 14 | `stacks/migrate.go` (copy, verify, cleanup, size, validate, skip set) | correct — `j.SourceNS`/`j.TargetNS` are set only by `NamespaceRoot` (accepted by name) |
| 4 + 3 | `appbackup/paths.go`, `backup/appbackup_bridge.go` | correct — `nsRoot` parameters |
| 15 | `backup/` capture, restore, tier-2, update guard, prune, kept load | correct — `nsRoot`, `sourceNsRoot` or `m.namespaceRoot(...)` |
| 1 | `api/router.go` removeStack | correct — `nsRoot` |
| 2 + 1 + 1 | `backup/removed_units.go`, `offbox_placement.go`, `offbox.go` | correct — the loop variable came from `namespaceRoot`; renamed `nsRoot` |
| 1 | `stacks/delete.go` GetStackBackupData | correct (its caller passes `AppNamespaceRoot`); parameter renamed `drivePath` → `nsRoot` |
| 1 | `web/handlers.go` appDetailsForPath | correct only while the system drive is never a storage path — now through `NamespaceRootFor` (same path on an enrolled drive) |
No live defect. Red-proofs: the six named sites before the fix; a bare call inside package `backup`.
## 4. Redis (R-1096)
**6a — the proof on 9202 (docmost, the throwaway).** The new line reached the box through a catalog (the drill, copied
into the box; no password on the box) and the dashboard's restart:
- 100/100 probe keys back;
- `aof_enabled:0`, `rdb_last_bgsave_status:ok`;
- Redis log: „Loading RDB … keys loaded: 133".
- Then the nightly volume dump, FLUSHALL (0 keys) and the backups page's restore: 100/100 back.
**Damage:**
- Old form, 64 zero bytes written into the AOF 400 bytes before its end: exit 1, „Bad file format reading the append only
file … use ./redis-check-aof --fix". Control (undamaged copy): starts.
- New form on a full disk (1 MiB tmpfs): BGSAVE `err` („No space left on device"), the old `dump.rdb` byte-identical (same
sha256), and after a stop without saving Redis starts on it with 50 keys.
- New form, a half-cut `dump.rdb`: Redis also refuses to start („RDB ERROR DETECTED"). The write path never leaves one
(temp file, then rename).
**6b — the step-file reading, confirmed:**
- `09` L2259–2263: every step but the last has its own complete definition in `steps/<to>.yml`.
- `09` L2325: one press applies ONE step with its OWN definition; the last step is the current template.
- Code: `stacks/ladder.go:152` `nextLadderStep`, `:110` the step file path, `stacks/pin.go:336` `advancePinTo` pins that file.
- A frozen app gets nothing: `09` L2662; `sync/sync.go:545–546` (pinned and equal → the catalog template, fixes flow;
pinned and different → the stored definition, frozen whole).
- Nothing says a step file must change with its final template. `check-test-record.py` rule 4 checks only that a step
file names its step's images. **Step files unchanged** (romm 2, docmost 1, nextcloud 2, immich 2).
**6c — the change** (catalog `5d9450b`, pushed 07:31 UTC). Boxes read from the hub right before the push:
- demo-hp: romm, docmost, paperless-ngx;
- Tester 1: paperless-ngx;
- demo-felhom: none of the five;
- scratch 9202 (not hub-reporting): paperless-ngx, immich.
Checks after the push:
- **demo-hp 9201:** sync, then the dashboard's restart of each app. All three run `--appendonly no`, `aof_enabled:0`,
`rdb_last_bgsave_status:ok`, and are `running`. Keys stayed: RomM 153→150, Docmost 54→58, paperless 2→4 (the apps add
and drop their own keys).
- **9202:** the compose holds the new line; it switches at the next start.
- **Tester 1:** not reached. It syncs from a drill copy inside the box (R-1022); its paperless still runs AOF.
**One finding for the record:** sync alone does not restart a running app. The new line runs from the app's next start,
which the nightly backup gives every app.
Also done:
- gate `redis-persistence` (`--fast`, 9 decoy cases, red-proved);
- a `07` [DESIGN] paragraph;
- the research file updated;
- R-1117 opened for the deferred volume removal.
## 5. Versions, commits, fixtures
- **Controller 0.325.0** (MinAgent 0.158.1, no golden), built from `68af0e9`.
- Test builds: rc.1 (`94f4493`), rc.2 (`6ed2238`, the family store opens at start).
- Docs: `88aa65d`. Census correction: `85d60a1` (no new image).
- CI green: 1979, 1981, 1982.
- **Catalog:** `33a4b3b` (R-1115, CI 1977) and `5d9450b` (Redis, CI 1978).
- **felhom.eu:** `0314b8e7` (`reuse_refs_check.py`, R-1114, CI 1980), then this report's commit.
- **i18n:** no page changed; every parity fixture byte-identical. The escrow fix reuses the existing key
`login.msg.wrong_password` (same Hungarian bytes).
- **Drill catalog repo** (scratch): `9bad48a` (its tree set to live main), `daff3d2` (docmost's Redis line, for 6a).
- **Release timeline:**
- per-box floors 08:20:02 UTC;
- all three boxes on 0.325.0 by 08:20:16;
- box walk green (24 / 9 / 17 containers, none unhealthy; dashboards 200; https-only check OK);
- global floor 08:21:31 UTC (303 `floor_set`), read back from `hub.db` + WAL (integrity ok, `0.325.0=0.158.1`, no per-box floor) and from the hub's page.
**Instruction file edit** (allowed by §5): `app-catalog-felhom.eu/CLAUDE.md` L54–56.
- Before: „every gate except `image-resolvable` and `volume-persistence`".
- After: the same, plus `gate-decoys` with its ~3 minutes.
- Why: that gate is new today and is not in `--fast`.
**Poster:** no fact on `felhom-system-poster.facts.md` changed.
## 6. Rows
**Before 174. Opened 2** (R-1116, R-1117). **Closed 8** (R-1115, R-1111, R-1112, R-1113, R-1109, R-1110, R-1114, R-1096).
**After 168.** R-1022 got one dated sentence (the drill repo's state). `unproven.py`: NOT WALKED 35 of 55, unchanged.
## 7. Observations
1. The catalog's own runner test was red on main since 2026-10-16 night. NOT-A-FINDING: fixed today (count 18 → 21).
2. The controller, agent and felhom.eu runners do not run their own decoy suites. FILED: R-1116
3. The family store opened (and converted an old file) only on the first family request, so a box nobody visits through a family gate kept the old values. NOT-A-FINDING: found live on rc.1, fixed in 0.325.0 before release.
4. The escrow re-auth answered a wrong password in Hungarian only. NOT-A-FINDING: fixed in 0.325.0 (small).
5. A stacks test wrote the update journal into its package folder; the R-578 walk tripped on it once. NOT-A-FINDING: fixed (small); a probe run proved no other test does it.
6. Through the dashboard a share rename already applied before R-1109 (delete and add each change the compose). NOT-A-FINDING: recorded in R-1109's close; the label covers a controller update that changes smb.conf alone.
7. Sync alone does not restart a running app; a catalog fix runs from the app's next start. NOT-A-FINDING: by design (decision 4 keeps fixes flowing; the nightly backup restarts every app).
8. Tester 1 did not take the Redis change. NOT-A-FINDING: it follows the drill catalog by your ruling (R-1022, note added).
9. On 9202 a catalog URL change to the private drill repo made the clone fail and left the box with no templates until I pointed it back. NOT-A-FINDING: my own setting change on a scratch box; the running apps kept their definitions; the next good sync restored them.
10. Immich and Nextcloud keep a Redis volume they do not need. FILED: R-1117
11. My Samba tool logged the throwaway test-share password. NOT-A-FINDING: my tooling; scrubbed, the password replaced, the tool redacts now.
12. `setup-gate.key` sits beside `family.json`, so someone who reads the data folder could still forge a family app cookie. NOT-A-FINDING: reading that folder already gives every key of the box; R-1112 removed the one value that worked without a key.
13. The launcher asks for SVG logos that do not exist (404). NOT-A-FINDING: as last night — it falls back to the PNG.
## 8. Teardown (three layers)
- **Machine:**
- 9202: docmost removed through the product (no volume left), the catalog setting put back (live catalog, controller
restarted), the damage-test folder removed, no `r1096-*` container left. 9202 now runs controller 0.324.0 → it was
0.314.0 before; I moved it to 0.324.0 by hand for the proof (it is set by hand on purpose, `nodes.md`).
- 9201: MeTube removed through the product (its empty media folder removed by hand), sharing turned off, the test
shares and their folder removed, the test password replaced first. 9201 runs 0.325.0.
- **Host:** nothing.
- **Hub:** per-box floors set and cleared; the global floor raised to 0.325.0 / MinAgent 0.158.1.
+3 -1
View File
@@ -2,10 +2,12 @@
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
**Updated 2026-10-17 (night).** Controller 0.324.0 is the floor for every box since 02:46 UTC (all three test boxes made tonight's off-site backup on it first): a restore now brings the database password back with the data (your 1A), restic 0.19.1 (your 2A), dashboard hardening — `REPORT-night-2026-10-17.md`. **Earlier (2026-10-16 evening):** 0.323.0, Debian 13 — `REPORT-debian13-restic-2026-10-16.md`. **Earlier (midday):** Controller 0.319.0 (the app page in panels, the login in one panel) is the floor, MinAgent 0.158.1 — `REPORT-app-page-tuning-2026-10-16.md`. **Earlier (morning):** Storage identity build B shipped: agent 0.158.1 and golden 0.318.0 vouched by you, controller 0.318.0 is the floor (MinAgent 0.158.1), installer 1.33.0 published — new one-disk boxes get their own data area. Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
**Updated 2026-10-17 (day).** Controller 0.325.0 is the floor for every box since 08:21 UTC (after the three test boxes): two more password doors count a try before checking it, family sign-ins are stored as fingerprints, a file-share rename always applies; five apps' Redis saves snapshots now, which a full disk cannot damage — `REPORT-burndown-3-2026-10-17.md`. **Earlier (2026-10-17 night):** controller 0.324.0 was the floor for every box since 02:46 UTC (all three test boxes made tonight's off-site backup on it first): a restore now brings the database password back with the data (your 1A), restic 0.19.1 (your 2A), dashboard hardening — `REPORT-night-2026-10-17.md`. **Earlier (2026-10-16 evening):** 0.323.0, Debian 13 — `REPORT-debian13-restic-2026-10-16.md`. **Earlier (midday):** Controller 0.319.0 (the app page in panels, the login in one panel) is the floor, MinAgent 0.158.1 — `REPORT-app-page-tuning-2026-10-16.md`. **Earlier (morning):** Storage identity build B shipped: agent 0.158.1 and golden 0.318.0 vouched by you, controller 0.318.0 is the floor (MinAgent 0.158.1), installer 1.33.0 published — new one-disk boxes get their own data area. Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
`documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`.
## What works
- **A full disk can no longer leave an app's Redis broken** (catalog, 2026-10-17): RomM, Docmost, paperless-ngx, Immich and Nextcloud save their small Redis store as snapshots. Proven first on the scratch box: 100 of 100 test keys kept through the switch and through a backup and restore. A box takes it at the app's next start (normally that night's backup). Tester 1 waits: it still follows the drill catalog (R-1022).
- **Two more password doors count a guess before checking it** (controller 0.325.0): the share-link password and the off-site wizard's password. Family sign-ins are stored as fingerprints; signed-in family members stay signed in.
- **A restore brings the database password back with the data** (controller 0.324.0, your decision 1A): remove an app, install it again, restore it — the app opens its own data (proven on demo-hp with Rallly and Grimmory, off-site and on-box, and through „use my kept data"). A backup without the password is refused before anything changes, with a plain sentence. Backup program restic 0.19.1 (your 2A): old and new copies read both ways; scanner findings in it 50 → 19.
- **The dashboard is harder to attack** (0.324.0): no sending the browser to another site after sign-in, no framing, parallel password guesses counted, 12-character minimum, reset-code requests limited, sign-out needs a token (the old sign-out link did nothing), a password change ends every family and gate session.
- **The box's backup program is now chosen, not inherited** (controller 0.323.0, 2026-10-16): restic 0.18.1, checked by its signature and fingerprint at every build, on Debian 13. Every way the box reads restic was compared old vs new with real outputs; old copies stay readable by the new program and new copies by the old one (on demo-hp's real store). The file-sharing image has 0 known high holes (was 48), and the same household password still works after the change.
@@ -184,6 +184,16 @@ nothing in this document's model: a destructive op still needs the operator's si
make. What a token narrows is the hub side: the operator actions above are `hosts:write`; the reveal of a break-glass
credential, every delete and reset, the password and the tokens are session only.
### 6.3 The box's own password doors (controller) [controller v0.324.0 / v0.325.0, R-983, R-1111, R-1112]
Outside this document's signing model, but recorded here because it is where a reader looks for "who may get in": the
controller has four password doors — the dashboard sign-in, the escrow wizard's re-auth (it shares the sign-in's
counter), a share link's password (`/s/<token>`, its own counter) and the family gate (its own, per visitor and per
name). **Each reserves a try BEFORE the slow password compare, in one locked step, and a right password clears it**
(the sign-in, the re-auth and a share: 5 tries per visitor in a sliding minute, `auth.go` `loginMaxAttempts`; `REUSE.md` row `web.reserveLoginAttempt`), so parallel tries cannot all pass a check none of
them has counted yet. **Sessions are stored as fingerprints, never as their cookie values** — dashboard (R-35) and family
(`family.json`, R-1112) alike. A password change or reset ends every dashboard, family and setup-gate session (R-976).
## 7. Hardware readiness (Viktor's "build the foundation now")
Software `ssh-ed25519` now; a FIDO2 `sk-ssh-ed25519@openssh.com` key later is a **no-op on the
@@ -560,6 +560,18 @@ compose DECLARES (`backup.ResolveDockerVolumeNames`) and the drive folders it bi
paths (`audits/burndown-apps-2026-10-16/P1/`). A survey of every running container on demo-hp found no other data path
in an anonymous volume (PrivateBin's are temp folders).
**[DESIGN, 2026-10-17, R-1096] A catalog Redis service saves snapshots (RDB), never an append-only file (AOF).** An AOF
damaged in the MIDDLE — what the 2026-09-24 thin-pool fill left in two boxes' Redis — makes Redis refuse to start until a
person runs `redis-check-aof --fix`, and the app stays down; a snapshot is written to a temp file and renamed only when
complete, so a full disk leaves the old snapshot readable (measured on scratch 9202: the save fails, the old `dump.rdb`
is byte-identical, Redis starts on it). The cost is the writes since the last snapshot — queue entries and sessions,
which these apps rebuild or ask again for. The Redis volume still rides the nightly volume dump, taken with the stack
stopped, so Redis writes a final snapshot first (100/100 probe keys back through dump + restore on 9202). romm, docmost,
paperless-ngx, immich and nextcloud switched (`--appendonly no`, catalog `5d9450b`); outline and dawarich already ran
RDB. A box applies the line at the app's next start (the nightly backup stops and starts each app); `steps/` files keep
what they were tested with (`09` §6.4 part 5). Catalog gate `redis-persistence` refuses AOF in a template. Evidence:
`audits/burndown-3-2026-10-17/P6/`; research `audits/night-restore-2026-10-17/P6/redis-persistence.md`.
### 6.2 Coverage per app class — and an unresolved count
**[FACT]** Of 53 catalog templates, **52 keep data in Docker named volumes**; exactly **13** carry a
@@ -0,0 +1,283 @@
ok FACT: docmost-postgres major + 1 (read from the clone) rc=1 (expected 1)
ok FACT: docmost-postgres major + 1, alone rc=1 (expected 1)
ok FACT: kimai-db 11.6 -> 12.3 BUNDLED with the kimai app bump rc=1 (expected 1)
ok FACT: kimai-db mariadb:11.6 -> mariadb:lts (major unreadable) rc=2 (expected 2)
ok GENUINE: kimai-db mariadb:11.x -> 11.99 (within major) rc=0 (expected 0)
ok GENUINE: kimai-db mariadb:11.6 -> 12.3 ALONE (the R-469 lift) rc=0 (expected 0)
ok GENUINE: docmost-postgres major+1 ALONE with its proven two-venue MARKED entry rc=0 (expected 0)
ok DECOY: the entry is proven on both venues but carries NO conversion mark rc=1 (expected 1)
ok DECOY: the marked entry cites ONE venue only (no box_evidence) rc=1 (expected 1)
ok DECOY: the mark sits in a COMMENT, the entry has none rc=1 (expected 1)
ok DECOY: the marked entry, but the move is BUNDLED with the app's own bump rc=1 (expected 1)
ok FACT: adventurelog's postgis 16 -> 17 (the postgis family was never judged before) rc=1 (expected 1)
ok DECOY: major moves only in a comment + serverVersion env rc=0 (expected 0)
ok DECOY: the APP image crosses a major (kimai 2.57 -> 3.0) rc=0 (expected 0)
ok DECOY: 'mariadb:12.3' lands in README.md, not a template rc=0 (expected 0)
ok FACT: kimai image moves, catalog_since untouched rc=1 (expected 1)
ok FACT: kimai image moves, catalog_since set to a FUTURE year rc=1 (expected 1)
ok GENUINE: kimai image moves AND catalog_since = today rc=0 (expected 0)
ok DECOY: image moves; today's date lands in a COMMENT and README, the field stays rc=1 (expected 1)
ok DECOY: only a comment + env line change, images untouched, date untouched rc=0 (expected 0)
ok FACT: a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT: an em dash in a Hungarian string (R-932) rc=1 (expected 1)
ok FACT: a Hungarian first_step removed rc=1 (expected 1)
ok FACT: a NEW app is not in the freeze rc=1 (expected 1)
ok FACT: an unknown key inside the English block rc=1 (expected 1)
ok FACT: an English deploy field with no Hungarian twin rc=1 (expected 1)
ok FACT: an accented Hungarian letter left in the English rc=1 (expected 1)
ok FACT: ASCII-only Hungarian left in the English (no accent to find) rc=1 (expected 1)
ok FACT: the product begs ("please") rc=1 (expected 1)
ok FACT: an English retrieval promise the Hungarian never made rc=1 (expected 1)
ok GENUINE: a REGISTERED true retrieval promise passes rc=0 (expected 0)
ok FACT: registered for ANOTHER app - the promise still convicts, the entry is stale rc=1 (expected 1)
ok FACT: registered on the path, but the sentence was rewritten (match gone) rc=1 (expected 1)
ok FACT: a STALE entry - nothing in the English promises it any more rc=1 (expected 1)
ok FACT: a registered promise with a two-word reason rc=1 (expected 1)
ok FACT: a credential token rewritten in translation rc=1 (expected 1)
ok FACT: a credential value reworded in translation (one digit added) rc=1 (expected 1)
ok FACT: an i18n block for a language the controller does not render rc=1 (expected 1)
ok FACT: an English list with a different number of steps rc=1 (expected 1)
ok FACT: ratchet — fewer strings translated than the ceiling records rc=1 (expected 1)
ok FACT: ratchet — more translated than the ceiling records rc=1 (expected 1)
ok GENUINE: a correct English block on privatebin rc=0 (expected 0)
ok GENUINE: naming an app does not change the coverage count rc=0 (expected 0)
ok FACT(no-yaml): a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT(no-yaml): a frozen Hungarian line deleted rc=1 (expected 1)
ok FACT(no-yaml): a NEW app is not in the freeze rc=1 (expected 1)
ok GENUINE(no-yaml): the untouched tree passes, and SAYS what it did not check rc=0 (expected 0)
ok GENUINE(no-yaml): romm's escaped-quote help_texts are still found rc=0 (expected 0)
ok DECOY: Hungarian rewritten inside a YAML COMMENT rc=0 (expected 0)
ok DECOY: a frozen Hungarian sentence pasted into README.md rc=0 (expected 0)
ok DECOY: display_name changed - a NAME, never copy rc=0 (expected 0)
ok DECOY: docs_url changed - configuration, never copy rc=0 (expected 0)
ok DECOY: Hungarian text added to a docker-compose.yml rc=0 (expected 0)
-- probe-matches-compose: the facts (each MUST be refused)
ok FACT: tandoor's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: wger's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: zipline's real R-618 path fault, re-introduced rc=1 (expected 1)
ok GENUINE: the fixed tree passes rc=0 (expected 0)
ok FACT: a clean app given a wrong probe port rc=1 (expected 1)
ok FACT: api+expect probe given a path the app does not answer rc=1 (expected 1)
-- probe-matches-compose: the decoys (the label moves, the fact does not)
ok DECOY: the port moves in a COMMENT in .felhom.yml rc=0 (expected 0)
ok DECOY: traefik loadbalancer.server.port changed rc=0 (expected 0)
ok DECOY: a published `ports:` mapping changed rc=0 (expected 0)
ok DECOY: a NON-probed sidecar's healthcheck port changed rc=0 (expected 0)
ok DECOY: path mismatch on a probe that CANNOT fail on it -> WARN, not FAIL rc=0 (expected 0)
-- probe-matches-compose: the DEGRADED mode CI actually runs (no PyYAML)
ok DEGRADED: the fixed tree passes, and SAYS it is degraded rc=0 (expected 0)
ok DEGRADED FACT: tandoor's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: wger's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: zipline's path fault still refused rc=1 (expected 1)
ok DEGRADED DECOY: traefik label moves, the fact does not rc=0 (expected 0)
-- probe-matches-compose: the probe TARGET (R-630)
ok FACT: paperless-ngx without its explicit container is refused rc=1 (expected 1)
ok FACT: immich's FOUR prefix candidates are an ambiguity, not a pick rc=1 (expected 1)
ok FACT: an explicit container no service declares is refused rc=1 (expected 1)
ok GENUINE: both explicit containers resolve rc=0 (expected 0)
ok DECOY: the container name moves in a COMMENT, not the field rc=0 (expected 0)
ok DECOY: a UNIQUE prefix still resolves without the field rc=0 (expected 0)
ok DEGRADED: the explicit container is read without PyYAML too rc=0 (expected 0)
ok DEGRADED FACT: removing it still refuses rc=1 (expected 1)
-- test-record-move: an image move needs its own proven record
ok FACT: a bare image move, no entry rc=1 (expected 1)
ok GENUINE: a proven entry whose digest the registry serves rc=0 (expected 0)
ok FACT: the entry's verdict is failed rc=1 (expected 1)
ok FACT: the registry now serves another digest rc=1 (expected 1)
ok INCONCLUSIVE: the registry cannot be asked rc=2 (expected 2)
ok DECOY: the entry only in a COMMENT under update_ladder rc=1 (expected 1)
ok DECOY: the entry only in README.md rc=1 (expected 1)
ok FACT: a new move carrying a BACKFILLED entry rc=1 (expected 1)
ok FACT: memory_tight and the limit did not move rc=1 (expected 1)
ok GENUINE: memory_tight WITH the limit raised rc=0 (expected 0)
ok DECOY: a ref moves in a compose COMMENT only rc=0 (expected 0)
-- test-record (static): the newest step IS the compose
ok GENUINE: a ladder whose head is the compose rc=0 (expected 0)
ok FACT: the compose moved past the ladder's head rc=1 (expected 1)
ok FACT: a line that is not one JSON entry rc=1 (expected 1)
ok FACT: a gap between steps rc=1 (expected 1)
ok FACT: a failed verdict sits in the ladder rc=1 (expected 1)
-- test-record (static): every intermediate step carries its own definition
ok FACT: a two-step ladder with NO steps/ file for the first step rc=1 (expected 1)
ok GENUINE: a two-step ladder whose first step carries its definition rc=0 (expected 0)
ok DECOY: the steps/ file has the right NAME and names the head's image rc=1 (expected 1)
ok DECOY: the step's definition sits beside the template under another name rc=1 (expected 1)
ok FACT: the step has its compose but no .felhom.yml rc=1 (expected 1)
ok DECOY: the step's .felhom.yml exists by NAME and holds no healthcheck rc=1 (expected 1)
-- test-record: a same-tag re-test (decision 52)
ok GENUINE: head + a re-test from its digest, both venues rc=0 (expected 0)
ok FACT: a re-test with NO new digest rc=1 (expected 1)
ok FACT: a re-test without the box venue rc=1 (expected 1)
ok FACT: a re-test FROM a digest the previous entry never tested rc=1 (expected 1)
ok DECOY: a re-test that names digest_from only in its evidence text rc=1 (expected 1)
ok GENUINE: a re-test whose digest the registry serves now rc=0 (expected 0)
ok FACT: a re-test whose digest the registry no longer serves rc=1 (expected 1)
ok FACT: a re-test with no new digest reaches the move gate too rc=1 (expected 1)
ok probe-measured: genuine komga note passes rc=0 (expected 0)
ok probe-measured: note moved into the TAGLINE rc=1 (expected 1)
ok probe-measured: date but no before/after rc=1 (expected 1)
ok probe-measured: before/after but no date rc=1 (expected 1)
ok probe-measured: 'read upstream' is not a measurement rc=1 (expected 1)
-- copy-i18n: the user guide (each FACT must be refused, naming the key)
ok GENUINE: a full Hungarian guide with its English overlay rc=0 (expected 0)
ok FACT: guide strings that are not in the freeze rc=1 (expected 1)
ok FACT: checked_version in the English block rc=1 (expected 1)
ok FACT: a section with an empty steps list rc=1 (expected 1)
ok FACT: an FAQ entry with a question and no answer rc=1 (expected 1)
ok FACT: an unquoted checked_version (YAML reads 4.10 as 4.1) rc=1 (expected 1)
ok FACT: more English sections than Hungarian rc=1 (expected 1)
ok FACT: an English section with a different step count rc=1 (expected 1)
ok FACT: an English guide with no Hungarian guide rc=1 (expected 1)
ok FACT: a tracker named in an English step rc=1 (expected 1)
ok FACT: a tracker named in a Hungarian answer (folded, any case) rc=1 (expected 1)
ok DECOY: a tracker named only in a YAML comment rc=0 (expected 0)
ok DECOY: a source-like word only in docs_url (configuration) rc=0 (expected 0)
-- guide-version (ADVISORY: WARN on the fact, silent on the label, exit 0 always)
ok FACT: the app's image moved off the checked tag (4.0.20 -> 4.0.21) rc=0 (advisory)
ok GENUINE: the guide is current rc=0 (advisory)
ok DECOY: the checked tag survives only on a SIDECAR rc=0 (advisory)
ok DECOY: checked_version only in a COMMENT rc=0 (advisory)
ok DECOY: checked_version only inside the English block rc=0 (advisory)
ok FACT: an empty checked_version is HIDDEN and named rc=0 (advisory)
ok image-pins: FACT: untagged rc=1 (expected 1)
ok image-pins: FACT: a registry PORT is not a tag rc=1 (expected 1)
ok image-pins: FACT: quoted :latest rc=1 (expected 1)
ok image-pins: FACT: :LATEST in capitals rc=1 (expected 1)
ok image-pins: FACT: a floating alias (:edge) rc=1 (expected 1)
ok image-pins: FACT: untagged with a comment saying pinned rc=1 (expected 1)
ok image-pins: FACT: a QUOTED key is the same field rc=1 (expected 1)
ok image-pins: FACT: an interpolated ref cannot be read rc=1 (expected 1)
ok image-pins: FACT: @sha256: with no digest behind it rc=1 (expected 1)
ok image-pins: INERT: a commented-out untagged image rc=0 (expected 0)
ok image-pins: INERT: an x- extension field (Compose ignores it) rc=0 (expected 0)
ok image-pins: INERT: an untagged image in README.md rc=0 (expected 0)
ok image-pins: GENUINE: a concrete tag rc=0 (expected 0)
ok image-pins: GENUINE: a registry port WITH a tag rc=0 (expected 0)
ok image-pins: GENUINE: a real digest rc=0 (expected 0)
ok image-pins: GENUINE: :latest pinned by its digest rc=0 (expected 0)
ok image-pins: GENUINE: the real catalog rc=0 (expected 0)
ok image-resolvable: GENUINE: every pin resolves rc=0 (expected 0)
ok image-resolvable: FACT: the registry says one pin is gone rc=1 (expected 1)
ok image-resolvable: LABEL: rc=0 carrying a throttle message rc=2 (expected 2)
ok image-resolvable: LABEL: rc=0 carrying any error text rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: a throttle on rc=1 is not an accusation rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: an unrecognised failure is not an accusation rc=2 (expected 2)
ok image-resolvable: LABEL: a docker that resolves EVERYTHING, the canary too rc=3 (expected 3)
ok image-resolvable: LABEL: no docker at all rc=2 (expected 2)
ok image-resolvable: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: injected-prober suite OK
ok volume-persistence: LABEL: a runtime that answers nothing rc=3 (expected 3)
ok volume-persistence: LABEL: no docker at all rc=3 (expected 3)
ok volume-persistence: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: LABEL: not a scratch host rc=3 (expected 3)
-- onboarding: the facts (each MUST be refused)
ok FACT: a new template with NO record rc=1 (expected 1)
ok FACT: a record missing id 1.4 rc=1 (expected 1)
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
ok FACT: done with a path that does not exist rc=1 (expected 1)
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
ok FACT: n/a with a two-word reason rc=1 (expected 1)
ok FACT: an OPEN row rc=1 (expected 1)
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
-- onboarding: the genuine articles (each MUST pass)
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
-- family-gate: genuine and decoys
ok GENUINE: a family app, literal exceptions, min 0.287.0, golden 0.287.0 rc=0 (expected 0)
ok DECOY: family_gate only in a COMMENT -> not gated, nothing owed rc=0 (expected 0)
ok DECOY: no sibling -> rule 3 stated NOT CHECKED rc=0 (expected 0)
-- family-gate: the facts (each MUST be refused)
ok FACT: an exception that is a regex rc=1 (expected 1)
ok FACT: the exception '/' (the whole app) rc=1 (expected 1)
ok FACT: an exception with '..' rc=1 (expected 1)
ok FACT: an exception list with no gate rc=1 (expected 1)
ok FACT: min_controller only in a comment rc=1 (expected 1)
ok FACT: min_controller below the release rc=1 (expected 1)
ok FACT: newest baked golden below the release rc=1 (expected 1)
ok FACT: a golden DIRECTORY 0.287.0 with no bake log (a name is not a bake) rc=1 (expected 1)
ok FACT: glance (ruled family-gated, decision 221) without family_gate rc=1 (expected 1)
ok FACT: glance with family_gate only in a COMMENT (the label without the fact) rc=1 (expected 1)
ok FACT: grimmory (ruled, decisions 63/64) with family_gate: false rc=1 (expected 1)
ok GENUINE: glance gated, no exceptions rc=0 (expected 0)
-- mem-limit-sum: genuine and decoys
ok GENUINE: 384+256 = 640M, the field says 640M rc=0 (expected 0)
ok GENUINE: 1G is 1024M (1G + 256M = 1280M) rc=0 (expected 0)
ok DECOY: a steps/ file with an old figure is not judged rc=0 (expected 0)
-- mem-limit-sum: the facts (each MUST be refused)
ok FACT: the field under the sum; the right figure only in comments rc=1 (expected 1)
ok FACT: reservations: memory makes nothing a limit (service w/o limit) rc=1 (expected 1)
ok FACT: mem_limit only OUTSIDE resources: (top level) is no declaration rc=1 (expected 1)
ok FACT: an unreadable size is INCONCLUSIVE, never a pass rc=2 (expected 2)
-- db-credentials: genuine and decoys
ok GENUINE + DECOYS: comment, app-service ref, redis, literal - all not judged rc=0 (expected 0)
-- db-credentials: the facts (each MUST be refused)
ok FACT: list form, the variable has no deploy field rc=1 (expected 1)
ok FACT: list form, fed by a `type: password` field rc=1 (expected 1)
ok FACT: map form, the variable has no deploy field rc=1 (expected 1)
-- data-key: genuine and decoys
ok GENUINE: a flagged *_ENCRYPTION_KEY; a signing key labelled 'Encryption key' unflagged; bookstack registered rc=0 (expected 0)
-- data-key: the facts (each MUST be refused)
ok FACT: the flag only in a COMMENT rc=1 (expected 1)
ok FACT: the flag only inside the i18n: block rc=1 (expected 1)
ok FACT: a REGISTERED data key unflagged (bookstack APP_KEY) rc=1 (expected 1)
ok FACT: an unexplained flag (a signing key flagged) rc=1 (expected 1)
ok FACT: a stale REGISTRY entry (bookstack has no APP_KEY any more) rc=1 (expected 1)
ok FACT: a non-boolean flag is INCONCLUSIVE rc=2 (expected 2)
-- unclean-boot-images: genuine and decoys
ok GENUINE: the block lists the server's image rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (flow list) rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (block list) rc=0 (expected 0)
ok DECOY: the new image only in a compose COMMENT rc=0 (expected 0)
ok DECOY: ANOTHER service bumped (machine-learning) rc=0 (expected 0)
ok DECOY: the block COMMENTED OUT, server bumped (no block, nothing owed) rc=0 (expected 0)
ok DECOY: a template with no block rc=0 (expected 0)
-- unclean-boot-images: the facts (each MUST be refused)
ok FACT: the server bumped, the list unchanged rc=1 (expected 1)
ok FACT: the same in a steps/ file (the ladder) rc=1 (expected 1)
ok FACT: the block names a service the compose does not run rc=1 (expected 1)
ok FACT: the block lists no images rc=1 (expected 1)
-- login-kind: genuine and decoys
ok GENUINE: login_kind first_visit rc=0 (expected 0)
ok GENUINE: login_kind email rc=0 (expected 0)
ok GENUINE: login_kind none rc=0 (expected 0)
ok GENUINE: quoted value rc=0 (expected 0)
ok DECOY: a bad value only in a COMMENT rc=0 (expected 0)
ok DECOY: a bad value only in the English block rc=0 (expected 0)
ok DECOY: the key under ANOTHER mapping rc=0 (expected 0)
-- login-kind: the facts (each MUST be refused)
ok FACT: an unknown value rc=1 (expected 1)
ok FACT: a wrong case rc=1 (expected 1)
ok FACT: the key twice rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
-- image-volumes: genuine
ok GENUINE: a named volume AT /romm/assets rc=0 (expected 0)
ok GENUINE: a bind AT /romm/assets, quoted rc=0 (expected 0)
ok GENUINE: an image not in the registry is not judged rc=0 (expected 0)
-- image-volumes: decoys and facts (each MUST be refused)
ok DECOY: the mount only in a COMMENT rc=1 (expected 1)
ok DECOY: the mount on ANOTHER service rc=1 (expected 1)
ok DECOY: the PARENT /romm mounted rc=1 (expected 1)
ok DECOY: a CHILD /romm/assets/x mounted rc=1 (expected 1)
ok DECOY: only a top-level volume, never mounted rc=1 (expected 1)
ok FACT: a steps/ file without it rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
catalog gate decoys OK — 243 case(s), every label judged on its fact (R-421)
rc=0 seconds=196
@@ -0,0 +1,52 @@
ok GENUINE: a bind AT /romm/assets, quoted rc=0 (expected 0)
ok GENUINE: an image not in the registry is not judged rc=0 (expected 0)
-- image-volumes: decoys and facts (each MUST be refused)
ok DECOY: the mount only in a COMMENT rc=1 (expected 1)
ok DECOY: the mount on ANOTHER service rc=1 (expected 1)
ok DECOY: the PARENT /romm mounted rc=1 (expected 1)
ok DECOY: a CHILD /romm/assets/x mounted rc=1 (expected 1)
ok DECOY: only a top-level volume, never mounted rc=1 (expected 1)
ok FACT: a steps/ file without it rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
FAIL: FACT: a credential token rewritten in translation: rc=0 expected 1; missing ['credential token']
code-server 52/52, crafty-controller 57/57, dawarich 54/54, docmost 51/51, emby 22/22, ghost 54/54, gitea 54/54, glance 43/43, grafana 50/50, gramps-web 53/53, grimmory 57/57, grocy 54/54, home-assistant 50/50, homebox 50/50, homepage 43/43, immich 59/59, jellyfin 54/54, karakeep 52/52, kimai 53/53, komga 56/56, lubelogger 57/57, mealie 53/53, metube 51/51, n8n 45/45, navidrome 55/55, nextcloud 60/60, nzbget 55/55, onlyoffice 52/52, opengist 49/49, outline 53/53, paperless-ngx 66/66, papra 50/50, plant-it 48/48, plex 57/57, privatebin 45/45, prowlarr 44/44, qbittorrent 54/54, radarr 55/55, radicale 51/51, rallly 48/48, recipe-importer 52/52, romm 74/74, sabnzbd 60/60, seerr 50/50, sonarr 55/55, sparkyfitness 54/54, tandoor 49/49, termix 46/46, transmission 52/52, uptime-kuma 49/49, vaultwarden 56/56, vikunja 50/50, wanderer 52/52, wger 52/52, wishlist 47/47, zipline 52/52
copy-i18n: OK
real 3m12.242s
user 3m6.178s
sys 0m5.255s
1654: case_copy("FACT: a credential token rewritten in translation", clone,
1655- [(WG, lambda t: t.replace(" default_creds: 'admin / adminadmin'",
1656- " default_creds: 'Sign in: administrator / hunter2'"))],
1657- expect_rc=1, must_contain=("credential token 'admin'", "credential token 'adminadmin'"))
1658- # The value itself reworded (a digit token, CRED_TOKEN), with the account word kept.
1659- CW = "templates/calibre-web/.felhom.yml"
1660- case_copy("FACT: a credential value reworded in translation (one digit added)", clone,
1661- [(CW, lambda t: t.replace(" default_creds: 'admin / admin123'",
1662- " default_creds: 'admin / admin1234'"))],
1663- expect_rc=1, must_contain=("credential token 'admin123'", "calibre-web"))
1664- case_copy("FACT: an i18n block for a language the controller does not render", clone,
1665- [(PB, lambda t: t.rstrip("\n") + "\ni18n:\n de:\n description: \"Verschluesselte Notizen\"\n")],
1666- expect_rc=1, must_contain=("renders en only",))
1667- case_copy("FACT: an English list with a different number of steps", clone,
1668- [(PB, en_with(" - 'Share the link you get - the encryption key is inside the URL'\n", ""))],
1669- expect_rc=1, must_contain=("a list is replaced",))
1670-
1671- # THE RATCHET — the one thing --expect-missing does not test for the cases above, so it is
1672- # tested here explicitly, in BOTH directions. A ceiling that only convicts upwards can be
1673- # left behind by a push that translated more than it recorded.
1674- # The ABOVE case REMOVES privatebin's English (2026-10-05): it used to ADD it and rely on the rest
1675- # of the catalog being untranslated, so the day the catalog reached full coverage (ceiling 0) the
1676- # tree had nothing missing, the gate rightly said OK, and the case failed on its own premise.
1677- case_copy("FACT: ratchet — fewer strings translated than the ceiling records", clone,
1678- [(PB, strip_en)], expect_rc=1,
1679- must_contain=("English coverage", "ABOVE"),
1680- extra_args=("--expect-missing", "0"))
1681- case_copy("FACT: ratchet — more translated than the ceiling records", clone,
1682- [(PB, add_en())], expect_rc=1,
1683- must_contain=("English coverage", "BELOW"),
1684- extra_args=("--expect-missing", "999999"))
[exited with code 0]
@@ -0,0 +1,288 @@
ok FACT: docmost-postgres major + 1 (read from the clone) rc=1 (expected 1)
ok FACT: docmost-postgres major + 1, alone rc=1 (expected 1)
ok FACT: kimai-db 11.6 -> 12.3 BUNDLED with the kimai app bump rc=1 (expected 1)
ok FACT: kimai-db mariadb:11.6 -> mariadb:lts (major unreadable) rc=2 (expected 2)
ok GENUINE: kimai-db mariadb:11.x -> 11.99 (within major) rc=0 (expected 0)
ok GENUINE: kimai-db mariadb:11.6 -> 12.3 ALONE (the R-469 lift) rc=0 (expected 0)
ok GENUINE: docmost-postgres major+1 ALONE with its proven two-venue MARKED entry rc=0 (expected 0)
ok DECOY: the entry is proven on both venues but carries NO conversion mark rc=1 (expected 1)
ok DECOY: the marked entry cites ONE venue only (no box_evidence) rc=1 (expected 1)
ok DECOY: the mark sits in a COMMENT, the entry has none rc=1 (expected 1)
ok DECOY: the marked entry, but the move is BUNDLED with the app's own bump rc=1 (expected 1)
ok FACT: adventurelog's postgis 16 -> 17 (the postgis family was never judged before) rc=1 (expected 1)
ok DECOY: major moves only in a comment + serverVersion env rc=0 (expected 0)
ok DECOY: the APP image crosses a major (kimai 2.57 -> 3.0) rc=0 (expected 0)
ok DECOY: 'mariadb:12.3' lands in README.md, not a template rc=0 (expected 0)
ok FACT: kimai image moves, catalog_since untouched rc=1 (expected 1)
ok FACT: kimai image moves, catalog_since set to a FUTURE year rc=1 (expected 1)
ok GENUINE: kimai image moves AND catalog_since = today rc=0 (expected 0)
ok DECOY: image moves; today's date lands in a COMMENT and README, the field stays rc=1 (expected 1)
ok DECOY: only a comment + env line change, images untouched, date untouched rc=0 (expected 0)
ok FACT: a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT: an em dash in a Hungarian string (R-932) rc=1 (expected 1)
ok FACT: a Hungarian first_step removed rc=1 (expected 1)
ok FACT: a NEW app is not in the freeze rc=1 (expected 1)
ok FACT: an unknown key inside the English block rc=1 (expected 1)
ok FACT: an English deploy field with no Hungarian twin rc=1 (expected 1)
ok FACT: an accented Hungarian letter left in the English rc=1 (expected 1)
ok FACT: ASCII-only Hungarian left in the English (no accent to find) rc=1 (expected 1)
ok FACT: the product begs ("please") rc=1 (expected 1)
ok FACT: an English retrieval promise the Hungarian never made rc=1 (expected 1)
ok GENUINE: a REGISTERED true retrieval promise passes rc=0 (expected 0)
ok FACT: registered for ANOTHER app - the promise still convicts, the entry is stale rc=1 (expected 1)
ok FACT: registered on the path, but the sentence was rewritten (match gone) rc=1 (expected 1)
ok FACT: a STALE entry - nothing in the English promises it any more rc=1 (expected 1)
ok FACT: a registered promise with a two-word reason rc=1 (expected 1)
ok FACT: a credential value reworded in translation (one digit added) rc=1 (expected 1)
ok FACT: an i18n block for a language the controller does not render rc=1 (expected 1)
ok FACT: an English list with a different number of steps rc=1 (expected 1)
ok FACT: ratchet — fewer strings translated than the ceiling records rc=1 (expected 1)
ok FACT: ratchet — more translated than the ceiling records rc=1 (expected 1)
ok GENUINE: a correct English block on privatebin rc=0 (expected 0)
ok GENUINE: naming an app does not change the coverage count rc=0 (expected 0)
ok FACT(no-yaml): a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT(no-yaml): a frozen Hungarian line deleted rc=1 (expected 1)
ok FACT(no-yaml): a NEW app is not in the freeze rc=1 (expected 1)
ok GENUINE(no-yaml): the untouched tree passes, and SAYS what it did not check rc=0 (expected 0)
ok GENUINE(no-yaml): romm's escaped-quote help_texts are still found rc=0 (expected 0)
ok DECOY: Hungarian rewritten inside a YAML COMMENT rc=0 (expected 0)
ok DECOY: a frozen Hungarian sentence pasted into README.md rc=0 (expected 0)
ok DECOY: display_name changed - a NAME, never copy rc=0 (expected 0)
ok DECOY: docs_url changed - configuration, never copy rc=0 (expected 0)
ok DECOY: Hungarian text added to a docker-compose.yml rc=0 (expected 0)
-- probe-matches-compose: the facts (each MUST be refused)
ok FACT: tandoor's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: wger's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: zipline's real R-618 path fault, re-introduced rc=1 (expected 1)
ok GENUINE: the fixed tree passes rc=0 (expected 0)
ok FACT: a clean app given a wrong probe port rc=1 (expected 1)
ok FACT: api+expect probe given a path the app does not answer rc=1 (expected 1)
-- probe-matches-compose: the decoys (the label moves, the fact does not)
ok DECOY: the port moves in a COMMENT in .felhom.yml rc=0 (expected 0)
ok DECOY: traefik loadbalancer.server.port changed rc=0 (expected 0)
ok DECOY: a published `ports:` mapping changed rc=0 (expected 0)
ok DECOY: a NON-probed sidecar's healthcheck port changed rc=0 (expected 0)
ok DECOY: path mismatch on a probe that CANNOT fail on it -> WARN, not FAIL rc=0 (expected 0)
-- probe-matches-compose: the DEGRADED mode CI actually runs (no PyYAML)
ok DEGRADED: the fixed tree passes, and SAYS it is degraded rc=0 (expected 0)
ok DEGRADED FACT: tandoor's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: wger's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: zipline's path fault still refused rc=1 (expected 1)
ok DEGRADED DECOY: traefik label moves, the fact does not rc=0 (expected 0)
-- probe-matches-compose: the probe TARGET (R-630)
ok FACT: paperless-ngx without its explicit container is refused rc=1 (expected 1)
ok FACT: immich's FOUR prefix candidates are an ambiguity, not a pick rc=1 (expected 1)
ok FACT: an explicit container no service declares is refused rc=1 (expected 1)
ok GENUINE: both explicit containers resolve rc=0 (expected 0)
ok DECOY: the container name moves in a COMMENT, not the field rc=0 (expected 0)
ok DECOY: a UNIQUE prefix still resolves without the field rc=0 (expected 0)
ok DEGRADED: the explicit container is read without PyYAML too rc=0 (expected 0)
ok DEGRADED FACT: removing it still refuses rc=1 (expected 1)
-- test-record-move: an image move needs its own proven record
ok FACT: a bare image move, no entry rc=1 (expected 1)
ok GENUINE: a proven entry whose digest the registry serves rc=0 (expected 0)
ok FACT: the entry's verdict is failed rc=1 (expected 1)
ok FACT: the registry now serves another digest rc=1 (expected 1)
ok INCONCLUSIVE: the registry cannot be asked rc=2 (expected 2)
ok DECOY: the entry only in a COMMENT under update_ladder rc=1 (expected 1)
ok DECOY: the entry only in README.md rc=1 (expected 1)
ok FACT: a new move carrying a BACKFILLED entry rc=1 (expected 1)
ok FACT: memory_tight and the limit did not move rc=1 (expected 1)
ok GENUINE: memory_tight WITH the limit raised rc=0 (expected 0)
ok DECOY: a ref moves in a compose COMMENT only rc=0 (expected 0)
-- test-record (static): the newest step IS the compose
ok GENUINE: a ladder whose head is the compose rc=0 (expected 0)
ok FACT: the compose moved past the ladder's head rc=1 (expected 1)
ok FACT: a line that is not one JSON entry rc=1 (expected 1)
ok FACT: a gap between steps rc=1 (expected 1)
ok FACT: a failed verdict sits in the ladder rc=1 (expected 1)
-- test-record (static): every intermediate step carries its own definition
ok FACT: a two-step ladder with NO steps/ file for the first step rc=1 (expected 1)
ok GENUINE: a two-step ladder whose first step carries its definition rc=0 (expected 0)
ok DECOY: the steps/ file has the right NAME and names the head's image rc=1 (expected 1)
ok DECOY: the step's definition sits beside the template under another name rc=1 (expected 1)
ok FACT: the step has its compose but no .felhom.yml rc=1 (expected 1)
ok DECOY: the step's .felhom.yml exists by NAME and holds no healthcheck rc=1 (expected 1)
-- test-record: a same-tag re-test (decision 52)
ok GENUINE: head + a re-test from its digest, both venues rc=0 (expected 0)
ok FACT: a re-test with NO new digest rc=1 (expected 1)
ok FACT: a re-test without the box venue rc=1 (expected 1)
ok FACT: a re-test FROM a digest the previous entry never tested rc=1 (expected 1)
ok DECOY: a re-test that names digest_from only in its evidence text rc=1 (expected 1)
ok GENUINE: a re-test whose digest the registry serves now rc=0 (expected 0)
ok FACT: a re-test whose digest the registry no longer serves rc=1 (expected 1)
ok FACT: a re-test with no new digest reaches the move gate too rc=1 (expected 1)
ok probe-measured: genuine komga note passes rc=0 (expected 0)
ok probe-measured: note moved into the TAGLINE rc=1 (expected 1)
ok probe-measured: date but no before/after rc=1 (expected 1)
ok probe-measured: before/after but no date rc=1 (expected 1)
ok probe-measured: 'read upstream' is not a measurement rc=1 (expected 1)
-- copy-i18n: the user guide (each FACT must be refused, naming the key)
ok GENUINE: a full Hungarian guide with its English overlay rc=0 (expected 0)
ok FACT: guide strings that are not in the freeze rc=1 (expected 1)
ok FACT: checked_version in the English block rc=1 (expected 1)
ok FACT: a section with an empty steps list rc=1 (expected 1)
ok FACT: an FAQ entry with a question and no answer rc=1 (expected 1)
ok FACT: an unquoted checked_version (YAML reads 4.10 as 4.1) rc=1 (expected 1)
ok FACT: more English sections than Hungarian rc=1 (expected 1)
ok FACT: an English section with a different step count rc=1 (expected 1)
ok FACT: an English guide with no Hungarian guide rc=1 (expected 1)
ok FACT: a tracker named in an English step rc=1 (expected 1)
ok FACT: a tracker named in a Hungarian answer (folded, any case) rc=1 (expected 1)
ok DECOY: a tracker named only in a YAML comment rc=0 (expected 0)
ok DECOY: a source-like word only in docs_url (configuration) rc=0 (expected 0)
-- guide-version (ADVISORY: WARN on the fact, silent on the label, exit 0 always)
ok FACT: the app's image moved off the checked tag (4.0.20 -> 4.0.21) rc=0 (advisory)
ok GENUINE: the guide is current rc=0 (advisory)
ok DECOY: the checked tag survives only on a SIDECAR rc=0 (advisory)
ok DECOY: checked_version only in a COMMENT rc=0 (advisory)
ok DECOY: checked_version only inside the English block rc=0 (advisory)
ok FACT: an empty checked_version is HIDDEN and named rc=0 (advisory)
ok image-pins: FACT: untagged rc=1 (expected 1)
ok image-pins: FACT: a registry PORT is not a tag rc=1 (expected 1)
ok image-pins: FACT: quoted :latest rc=1 (expected 1)
ok image-pins: FACT: :LATEST in capitals rc=1 (expected 1)
ok image-pins: FACT: a floating alias (:edge) rc=1 (expected 1)
ok image-pins: FACT: untagged with a comment saying pinned rc=1 (expected 1)
ok image-pins: FACT: a QUOTED key is the same field rc=1 (expected 1)
ok image-pins: FACT: an interpolated ref cannot be read rc=1 (expected 1)
ok image-pins: FACT: @sha256: with no digest behind it rc=1 (expected 1)
ok image-pins: INERT: a commented-out untagged image rc=0 (expected 0)
ok image-pins: INERT: an x- extension field (Compose ignores it) rc=0 (expected 0)
ok image-pins: INERT: an untagged image in README.md rc=0 (expected 0)
ok image-pins: GENUINE: a concrete tag rc=0 (expected 0)
ok image-pins: GENUINE: a registry port WITH a tag rc=0 (expected 0)
ok image-pins: GENUINE: a real digest rc=0 (expected 0)
ok image-pins: GENUINE: :latest pinned by its digest rc=0 (expected 0)
ok image-pins: GENUINE: the real catalog rc=0 (expected 0)
ok image-resolvable: GENUINE: every pin resolves rc=0 (expected 0)
ok image-resolvable: FACT: the registry says one pin is gone rc=1 (expected 1)
ok image-resolvable: LABEL: rc=0 carrying a throttle message rc=2 (expected 2)
ok image-resolvable: LABEL: rc=0 carrying any error text rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: a throttle on rc=1 is not an accusation rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: an unrecognised failure is not an accusation rc=2 (expected 2)
ok image-resolvable: LABEL: a docker that resolves EVERYTHING, the canary too rc=3 (expected 3)
ok image-resolvable: LABEL: no docker at all rc=2 (expected 2)
ok image-resolvable: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: injected-prober suite OK
ok volume-persistence: LABEL: a runtime that answers nothing rc=3 (expected 3)
ok volume-persistence: LABEL: no docker at all rc=3 (expected 3)
ok volume-persistence: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: LABEL: not a scratch host rc=3 (expected 3)
-- onboarding: the facts (each MUST be refused)
ok FACT: a new template with NO record rc=1 (expected 1)
ok FACT: a record missing id 1.4 rc=1 (expected 1)
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
ok FACT: done with a path that does not exist rc=1 (expected 1)
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
ok FACT: n/a with a two-word reason rc=1 (expected 1)
ok FACT: an OPEN row rc=1 (expected 1)
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
-- onboarding: the genuine articles (each MUST pass)
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
-- family-gate: genuine and decoys
ok GENUINE: a family app, literal exceptions, min 0.287.0, golden 0.287.0 rc=0 (expected 0)
ok DECOY: family_gate only in a COMMENT -> not gated, nothing owed rc=0 (expected 0)
ok DECOY: no sibling -> rule 3 stated NOT CHECKED rc=0 (expected 0)
-- family-gate: the facts (each MUST be refused)
ok FACT: an exception that is a regex rc=1 (expected 1)
ok FACT: the exception '/' (the whole app) rc=1 (expected 1)
ok FACT: an exception with '..' rc=1 (expected 1)
ok FACT: an exception list with no gate rc=1 (expected 1)
ok FACT: min_controller only in a comment rc=1 (expected 1)
ok FACT: min_controller below the release rc=1 (expected 1)
ok FACT: newest baked golden below the release rc=1 (expected 1)
ok FACT: a golden DIRECTORY 0.287.0 with no bake log (a name is not a bake) rc=1 (expected 1)
ok FACT: glance (ruled family-gated, decision 221) without family_gate rc=1 (expected 1)
ok FACT: glance with family_gate only in a COMMENT (the label without the fact) rc=1 (expected 1)
ok FACT: grimmory (ruled, decisions 63/64) with family_gate: false rc=1 (expected 1)
ok GENUINE: glance gated, no exceptions rc=0 (expected 0)
-- mem-limit-sum: genuine and decoys
ok GENUINE: 384+256 = 640M, the field says 640M rc=0 (expected 0)
ok GENUINE: 1G is 1024M (1G + 256M = 1280M) rc=0 (expected 0)
ok DECOY: a steps/ file with an old figure is not judged rc=0 (expected 0)
-- mem-limit-sum: the facts (each MUST be refused)
ok FACT: the field under the sum; the right figure only in comments rc=1 (expected 1)
ok FACT: reservations: memory makes nothing a limit (service w/o limit) rc=1 (expected 1)
ok FACT: mem_limit only OUTSIDE resources: (top level) is no declaration rc=1 (expected 1)
ok FACT: an unreadable size is INCONCLUSIVE, never a pass rc=2 (expected 2)
-- db-credentials: genuine and decoys
ok GENUINE + DECOYS: comment, app-service ref, redis, literal - all not judged rc=0 (expected 0)
-- db-credentials: the facts (each MUST be refused)
ok FACT: list form, the variable has no deploy field rc=1 (expected 1)
ok FACT: list form, fed by a `type: password` field rc=1 (expected 1)
ok FACT: map form, the variable has no deploy field rc=1 (expected 1)
-- data-key: genuine and decoys
ok GENUINE: a flagged *_ENCRYPTION_KEY; a signing key labelled 'Encryption key' unflagged; bookstack registered rc=0 (expected 0)
-- data-key: the facts (each MUST be refused)
ok FACT: the flag only in a COMMENT rc=1 (expected 1)
ok FACT: the flag only inside the i18n: block rc=1 (expected 1)
ok FACT: a REGISTERED data key unflagged (bookstack APP_KEY) rc=1 (expected 1)
ok FACT: an unexplained flag (a signing key flagged) rc=1 (expected 1)
ok FACT: a stale REGISTRY entry (bookstack has no APP_KEY any more) rc=1 (expected 1)
ok FACT: a non-boolean flag is INCONCLUSIVE rc=2 (expected 2)
-- unclean-boot-images: genuine and decoys
ok GENUINE: the block lists the server's image rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (flow list) rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (block list) rc=0 (expected 0)
ok DECOY: the new image only in a compose COMMENT rc=0 (expected 0)
ok DECOY: ANOTHER service bumped (machine-learning) rc=0 (expected 0)
ok DECOY: the block COMMENTED OUT, server bumped (no block, nothing owed) rc=0 (expected 0)
ok DECOY: a template with no block rc=0 (expected 0)
-- unclean-boot-images: the facts (each MUST be refused)
ok FACT: the server bumped, the list unchanged rc=1 (expected 1)
ok FACT: the same in a steps/ file (the ladder) rc=1 (expected 1)
ok FACT: the block names a service the compose does not run rc=1 (expected 1)
ok FACT: the block lists no images rc=1 (expected 1)
-- login-kind: genuine and decoys
ok GENUINE: login_kind first_visit rc=0 (expected 0)
ok GENUINE: login_kind email rc=0 (expected 0)
ok GENUINE: login_kind none rc=0 (expected 0)
ok GENUINE: quoted value rc=0 (expected 0)
ok DECOY: a bad value only in a COMMENT rc=0 (expected 0)
ok DECOY: a bad value only in the English block rc=0 (expected 0)
ok DECOY: the key under ANOTHER mapping rc=0 (expected 0)
-- login-kind: the facts (each MUST be refused)
ok FACT: an unknown value rc=1 (expected 1)
ok FACT: a wrong case rc=1 (expected 1)
ok FACT: the key twice rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
-- image-volumes: genuine
ok GENUINE: a named volume AT /romm/assets rc=0 (expected 0)
ok GENUINE: a bind AT /romm/assets, quoted rc=0 (expected 0)
ok GENUINE: an image not in the registry is not judged rc=0 (expected 0)
-- image-volumes: decoys and facts (each MUST be refused)
ok DECOY: the mount only in a COMMENT rc=1 (expected 1)
ok DECOY: the mount on ANOTHER service rc=1 (expected 1)
ok DECOY: the PARENT /romm mounted rc=1 (expected 1)
ok DECOY: a CHILD /romm/assets/x mounted rc=1 (expected 1)
ok DECOY: only a top-level volume, never mounted rc=1 (expected 1)
ok FACT: a steps/ file without it rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
FAIL: FACT: a credential token rewritten in translation: rc=1 expected 1; missing ["credential token 'adminadmin'"]
53/53, grimmory 57/57, grocy 54/54, home-assistant 50/50, homebox 50/50, homepage 43/43, immich 59/59, jellyfin 54/54, karakeep 52/52, kimai 53/53, komga 56/56, lubelogger 57/57, mealie 53/53, metube 51/51, n8n 45/45, navidrome 55/55, nextcloud 60/60, nzbget 55/55, onlyoffice 52/52, opengist 49/49, outline 53/53, paperless-ngx 66/66, papra 50/50, plant-it 48/48, plex 57/57, privatebin 45/45, prowlarr 44/44, qbittorrent 54/54, radarr 55/55, radicale 51/51, rallly 48/48, recipe-importer 52/52, romm 74/74, sabnzbd 60/60, seerr 50/50, sonarr 55/55, sparkyfitness 54/54, tandoor 49/49, termix 46/46, transmission 52/52, uptime-kuma 49/49, vaultwarden 56/56, vikunja 50/50, wanderer 52/52, wger 52/52, wishlist 47/47, zipline 52/52
copy-i18n: FAIL — 1 finding(s)
- wger / i18n.en.app_info.default_creds: the credential token 'admin' is not in the English text: 'Sign in: administrator / hunter2'
rc=1 seconds=198
@@ -0,0 +1,201 @@
controller_gates — 18 gate(s) [--fast]
==============================================================================
== gate: template-id (template_id_gate.py)
==============================================================================
integrity gate OK — every JS element-ID reference resolves in the page it renders on
==============================================================================
== gate: emoji (emoji_gate.py)
==============================================================================
emoji gate OK — no emoji in web templates
==============================================================================
== gate: native-confirm (native_confirm_gate.py)
==============================================================================
native-confirm gate OK — no native confirm()/prompt()/alert() in templates
==============================================================================
== gate: offbox-rename (offbox_rename_gate.py)
==============================================================================
offbox rename gate OK — Tier-3 is 'Tavoli mentes' everywhere customer-facing (26 file(s) + 129 bundle value(s) named by them)
==============================================================================
== gate: app-row-dedup (app_row_dedup_gate.py)
==============================================================================
app_row_dedup_gate: OK (row markup single-sourced in app_row.html; 44 templates scanned)
==============================================================================
== gate: mojibake (mojibake_gate.py)
==============================================================================
mojibake_gate: OK (1187 files clean — no double-encoding signatures)
==============================================================================
== gate: docker-v (docker_run_volume_path_gate.py)
==============================================================================
docker -v gate OK — every volume mount is named-volume or proven host-visible
==============================================================================
== gate: secret-markup (secret_in_markup_gate.py)
==============================================================================
secret-in-markup gate OK — 44 templates, no secret-named expression rendered
(NAME-BASED: blind to a secret arriving under a neutral PAGE-DATA key — see the docstring)
==============================================================================
== gate: retrieval-promise (retrieval_promise_gate.py)
==============================================================================
retrieval-promise gate OK — 45 surface(s) incl. 1 Go handler file(s), 18 registered claim(s) + 16 English, none unregistered
(BLIND SPOT: it registers WHERE the claim is made, not whether each conditional is wired
to a true predicate — that is what the R-302 render tests are for.)
==============================================================================
== gate: debug-routes (debug_route_gate.py)
==============================================================================
debug route gate OK - 19 referenced address(es), all dispatched, none orphaned
==============================================================================
== gate: reuse-refs (reuse_refs_check.py /mnt/5_hdd/felhom.eu/git/felhom-controller)
==============================================================================
note [felhom-controller] line 15: backup/appbackup_bridge.go (resolved by suffix → controller/internal/backup/appbackup_bridge.go)
note [felhom-controller] line 19: appbackup/userdata.go (resolved by suffix → controller/internal/appbackup/userdata.go)
note [felhom-controller] line 19: stacks/skeleton_derive.go (resolved by suffix → controller/internal/stacks/skeleton_derive.go)
note [felhom-controller] line 64: internal/report/builder.go (resolved by suffix → controller/internal/report/builder.go)
note [felhom-controller] line 95: stacks/deploy.go (resolved by suffix → controller/internal/stacks/deploy.go)
note [felhom-controller] line 155: cmd/controller/main.go (resolved by suffix → controller/cmd/controller/main.go)
note [felhom-controller] line 172: internal/selfupdate/updater.go (resolved by suffix → controller/internal/selfupdate/updater.go)
note [felhom-controller] line 180: appbackup/appdata.go (resolved by suffix → controller/internal/appbackup/appdata.go)
note [felhom-controller] line 206: scripts/secret_in_markup_gate.py (resolved by suffix → controller/scripts/secret_in_markup_gate.py)
note [felhom-controller] line 258: hub/internal/api/handler.go (cross-repo → felhom.eu/hub/internal/api/handler.go)
note [felhom-controller] line 258: hub/internal/notify/dispatcher.go (cross-repo → felhom.eu/hub/internal/notify/dispatcher.go)
note [felhom-controller] line 275: monitor/deadline.go (cross-repo (suffix) → felhom.eu/hub/internal/monitor/deadline.go)
note [felhom-controller] line 320: wgsync/reconciler.go (cross-repo (suffix) → felhom.eu/hub/internal/wgsync/reconciler.go)
note [felhom-controller] line 325: templates/app_shell.html (resolved by suffix → controller/internal/web/templates/app_shell.html)
note [felhom-controller] line 361: api/router.go (resolved by suffix → controller/internal/api/router.go)
note [felhom-controller] line 378: cmd/controller/r856_wiring_test.go (resolved by suffix → controller/cmd/controller/r856_wiring_test.go)
note [felhom-controller] line 387: web/netprobe.go (resolved by suffix → controller/internal/web/netprobe.go)
note [felhom-controller] line 387: web/server.go (resolved by suffix → controller/internal/web/server.go)
note [felhom-controller] line 411: felhom.eu/scripts/golden_currency_gate.py (cross-repo → felhom.eu/scripts/golden_currency_gate.py)
OK [felhom-controller]: 231 cited paths — exact 212, suffix 14, ambiguous 0, cross-repo 5, FAILED 0 (siblings searched: app-catalog-felhom.eu, felhom-agent, felhom.eu, felhom.eu-media, felhom.eu-sizing-push, felhom.eu-wt-sizing)
==============================================================================
== gate: instructions (instructions_gate.py /mnt/5_hdd/felhom.eu/git/felhom-controller)
==============================================================================
instructions_gate: /mnt/5_hdd/felhom.eu/git/felhom-controller
CLAUDE.md effective lines : 92 (ceiling 200)
version literals : 0
TEMPORARY blocks : 0
rule files : 5 (4 path-scoped)
workspace file : SYMLINK -> felhom.eu/documentation/runbooks/workspace-CLAUDE.md (resolves to the versioned copy)
memory index : 200 lines (ceiling 200), 24321 bytes (ceiling 25600)
memory index content : 28 version literal(s), 3 host address(es), 0 expired statement(s) [WARN only]
memory topic files : 177 indexed, 4 orphaned, 41 archived
register citations : 12 cited, 1077 register items known
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:30: version literal '0.14.0' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- **[restic 0.14.0 `--verify` is size+mtime, NOT content](restic-0140-verify-and-lock-sema
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:90: version literal '0.142.0' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [Offsite continuity](offsite-continuity-2026-07-17.md) — ctrl 0.142.0 orphan guard + hub
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:90: version literal '0.60.0' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [Offsite continuity](offsite-continuity-2026-07-17.md) — ctrl 0.142.0 orphan guard + hub
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:97: version literal '0.152.0' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- **[Hub operator UI: CC uses its token HUB_TOKEN, not the password](hub-operator-ui-basic
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:101: version literal '0.90.1' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [Agent 0.90.1 is wrapper-only](agent-0901-is-wrapper-only.md) — do NOT publish the binar
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:103: version literal '0.144.0' — the fleet is not uniform, so it is stale within a day. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [SMB „Megosztás" R-7 slice 1](smb-sharing-r7-slice1-2026-07-18.md) — ctrl 0.144.0 + felh
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md: ... and 22 more version literal(s) — full list from the tally counts above.
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:59: host address '192.168.0.192' — operations/nodes.md is the single home for these. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [hub.felhom.eu resolves to the LAN here](hub-resolves-to-lan-on-dooplex-network.md) — 19
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:78: host address '167.233.158.164' — operations/nodes.md is the single home for these. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- [Offsite PBS box RAM ceiling](offsite-pbs-box-ram-ceiling-2026-07-27.md) — **root SSH =
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md:185: host address '192.168.0.180' — operations/nodes.md is the single home for these. Not a failure: Claude writes this file between sessions, so this warning is aimed at the model that will next edit it, not at whoever is pushing.
- **CC runs ON DooPlex** (192.168.0.180) — repos `/mnt/5_hdd/felhom.eu/git/felhom-*`, buil
WARNING: /mnt/5_hdd/felhom.eu/git/.claude-memory: 4 top-level topic file(s) are not referenced by MEMORY.md, so nothing will ever read them: f10-demo-hp-offsite-resolved-2026-07-23.md, launcher-v0163-2026-07-24.md, peti-return-p1-stop-2026-07-13.md, stopped-not-fault-v0164-2026-07-24.md
instructions_gate: OK (11 warning(s))
==============================================================================
== gate: observations (observations_gate.py /mnt/5_hdd/felhom.eu/git/felhom-controller)
==============================================================================
report : /mnt/5_hdd/felhom.eu/git/felhom-controller/REPORT.md
section : ## 4. Observations
observation items : 8
OK 1. FILED R-1109
OK 2. FILED R-1110
OK 3. FILED R-1111
OK 4. FILED R-1112
OK 5. FILED R-1113
OK 6. FILED R-1114
OK 7. NOT-A-FINDING
OK 8. NOT-A-FINDING
observations gate OK — every observation is either filed or explicitly declared
==============================================================================
== gate: minagent-header (minagent_header_gate.py)
==============================================================================
minagent header gate OK — v0.324.0 declares MinAgent 0.158.1
==============================================================================
== gate: i18n (i18n_missing_gate.py)
==============================================================================
i18n: 2051 markers in 44 templates; hu 2764 keys, en 2776 keys
i18n: English missing 0 (ceiling 0); Hungarian formal forms 0 (ceiling 0, R-516)
i18n gate OK
==============================================================================
== gate: go-parity (i18n_go_parity.py)
==============================================================================
go-parity: base capture 736f54b49610 (7467 literals), 773 slice-2 keys listed, 272 pre-existing, 776 named in Go, 585 helper calls judged for arity
go-parity gate OK: every Go-side key carries base-commit text, byte for byte, and every judged call passes as many arguments as its message has verbs.
==============================================================================
== gate: gofmt (gofmt_gate.py)
==============================================================================
gofmt gate OK — every Go file under cmd, internal is gofmt-clean
==============================================================================
== gate: golden-notice (golden_notice.py /mnt/5_hdd/felhom.eu/git/felhom-controller)
==============================================================================
==============================================================================
NOTICE — v0.324.0 OWES A GOLDEN. (this NEVER blocks; see the docstring)
==============================================================================
newest released controller : 0.324.0 (this repo's CHANGELOG.md)
newest golden baked : 0.318.0 (felhom.eu documentation/tests/)
A machine installed right now would receive 0.318.0, not 0.324.0.
This is a REMINDER AT THE ONE MOMENT IT IS USEFUL — you are in the repo where the
release happens. It does not block, and must not: at the moment a release is
committed the golden cannot exist yet.
WHAT CLEARS IT: bake a golden (felhom.eu documentation/runbooks/RUNBOOK-manual-build.md
section 4.1), then vouch it — a THREE-field change: golden_version + agent_version +
min_agent. The bake record lands in felhom.eu documentation/tests/golden-<ver>-<date>/.
If this release deliberately needs no golden, record a waiver row in
felhom.eu documentation/backlog/OPEN-ITEMS.md — never a habit of bypassing.
==============================================================================
==============================================================================
== summary
==============================================================================
template-id OK (exit 0)
emoji OK (exit 0)
native-confirm OK (exit 0)
offbox-rename OK (exit 0)
app-row-dedup OK (exit 0)
mojibake OK (exit 0)
docker-v OK (exit 0)
secret-markup OK (exit 0)
retrieval-promise OK (exit 0)
debug-routes OK (exit 0)
reuse-refs OK (exit 0)
instructions OK (exit 0)
observations OK (exit 0)
minagent-header OK (exit 0)
i18n OK (exit 0)
go-parity OK (exit 0)
gofmt OK (exit 0)
golden-notice ADVISORY (exit 0, advisory)
all controller gates OK
@@ -0,0 +1,178 @@
controller_gates — 18 gate(s) [--fast]
==============================================================================
== gate: template-id (template_id_gate.py)
==============================================================================
integrity gate OK — every JS element-ID reference resolves in the page it renders on
==============================================================================
== gate: emoji (emoji_gate.py)
==============================================================================
emoji gate OK — no emoji in web templates
==============================================================================
== gate: native-confirm (native_confirm_gate.py)
==============================================================================
native-confirm gate OK — no native confirm()/prompt()/alert() in templates
==============================================================================
== gate: offbox-rename (offbox_rename_gate.py)
==============================================================================
offbox rename gate OK — Tier-3 is 'Tavoli mentes' everywhere customer-facing (26 file(s) + 129 bundle value(s) named by them)
==============================================================================
== gate: app-row-dedup (app_row_dedup_gate.py)
==============================================================================
app_row_dedup_gate: OK (row markup single-sourced in app_row.html; 44 templates scanned)
==============================================================================
== gate: mojibake (mojibake_gate.py)
==============================================================================
mojibake_gate: OK (1187 files clean — no double-encoding signatures)
==============================================================================
== gate: docker-v (docker_run_volume_path_gate.py)
==============================================================================
docker -v gate OK — every volume mount is named-volume or proven host-visible
==============================================================================
== gate: secret-markup (secret_in_markup_gate.py)
==============================================================================
secret-in-markup gate OK — 44 templates, no secret-named expression rendered
(NAME-BASED: blind to a secret arriving under a neutral PAGE-DATA key — see the docstring)
==============================================================================
== gate: retrieval-promise (retrieval_promise_gate.py)
==============================================================================
retrieval-promise gate OK — 45 surface(s) incl. 1 Go handler file(s), 18 registered claim(s) + 16 English, none unregistered
(BLIND SPOT: it registers WHERE the claim is made, not whether each conditional is wired
to a true predicate — that is what the R-302 render tests are for.)
==============================================================================
== gate: debug-routes (debug_route_gate.py)
==============================================================================
debug route gate OK - 19 referenced address(es), all dispatched, none orphaned
==============================================================================
== gate: reuse-refs (reuse_refs_check.py /mnt/5_hdd/felhom.eu/wt/b3-gates)
==============================================================================
note [b3-gates] line 15: backup/appbackup_bridge.go (resolved by suffix → controller/internal/backup/appbackup_bridge.go)
note [b3-gates] line 19: appbackup/userdata.go (resolved by suffix → controller/internal/appbackup/userdata.go)
note [b3-gates] line 19: stacks/skeleton_derive.go (resolved by suffix → controller/internal/stacks/skeleton_derive.go)
note [b3-gates] line 64: internal/report/builder.go (resolved by suffix → controller/internal/report/builder.go)
note [b3-gates] line 95: stacks/deploy.go (resolved by suffix → controller/internal/stacks/deploy.go)
note [b3-gates] line 155: cmd/controller/main.go (resolved by suffix → controller/cmd/controller/main.go)
note [b3-gates] line 172: internal/selfupdate/updater.go (resolved by suffix → controller/internal/selfupdate/updater.go)
note [b3-gates] line 180: appbackup/appdata.go (resolved by suffix → controller/internal/appbackup/appdata.go)
note [b3-gates] line 206: scripts/secret_in_markup_gate.py (resolved by suffix → controller/scripts/secret_in_markup_gate.py)
note [b3-gates] line 258: hub/internal/api/handler.go (cross-repo → felhom.eu/hub/internal/api/handler.go)
note [b3-gates] line 258: hub/internal/notify/dispatcher.go (cross-repo → felhom.eu/hub/internal/notify/dispatcher.go)
note [b3-gates] line 275: monitor/deadline.go (cross-repo (suffix) → felhom.eu/hub/internal/monitor/deadline.go)
note [b3-gates] line 320: wgsync/reconciler.go (cross-repo (suffix) → felhom.eu/hub/internal/wgsync/reconciler.go)
note [b3-gates] line 325: templates/app_shell.html (resolved by suffix → controller/internal/web/templates/app_shell.html)
note [b3-gates] line 361: api/router.go (resolved by suffix → controller/internal/api/router.go)
note [b3-gates] line 378: cmd/controller/r856_wiring_test.go (resolved by suffix → controller/cmd/controller/r856_wiring_test.go)
note [b3-gates] line 387: web/netprobe.go (resolved by suffix → controller/internal/web/netprobe.go)
note [b3-gates] line 387: web/server.go (resolved by suffix → controller/internal/web/server.go)
note [b3-gates] line 411: felhom.eu/scripts/golden_currency_gate.py (cross-repo → felhom.eu/scripts/golden_currency_gate.py)
OK [b3-gates]: 231 cited paths — exact 212, suffix 14, ambiguous 0, cross-repo 5, FAILED 0 (siblings searched: app-catalog-felhom.eu, felhom-agent, felhom.eu, felhom.eu-media, felhom.eu-sizing-push, felhom.eu-wt-sizing)
==============================================================================
== gate: instructions (instructions_gate.py /mnt/5_hdd/felhom.eu/wt/b3-gates)
==============================================================================
instructions_gate: /mnt/5_hdd/felhom.eu/wt/b3-gates
CLAUDE.md effective lines : 92 (ceiling 200)
version literals : 0
TEMPORARY blocks : 0
rule files : 5 (4 path-scoped)
workspace copy : n/a (not this workspace)
memory index : absent (.claude-memory/ not on this host — the auto-memory store is machine-local by design, so a clone elsewhere legitimately has none; PASS with reason)
register citations : n/a (no OPEN-ITEMS.md — not this workspace)
instructions_gate: OK
==============================================================================
== gate: observations (observations_gate.py /mnt/5_hdd/felhom.eu/wt/b3-gates)
==============================================================================
report : /mnt/5_hdd/felhom.eu/wt/b3-gates/REPORT.md
section : ## 4. Observations
observation items : 8
OK 1. FILED R-1109
OK 2. FILED R-1110
OK 3. FILED R-1111
OK 4. FILED R-1112
OK 5. FILED R-1113
OK 6. FILED R-1114
OK 7. NOT-A-FINDING
OK 8. NOT-A-FINDING
observations gate OK — every observation is either filed or explicitly declared
==============================================================================
== gate: minagent-header (minagent_header_gate.py)
==============================================================================
minagent header gate OK — v0.324.0 declares MinAgent 0.158.1
==============================================================================
== gate: i18n (i18n_missing_gate.py)
==============================================================================
i18n: 2051 markers in 44 templates; hu 2764 keys, en 2776 keys
i18n: English missing 0 (ceiling 0); Hungarian formal forms 0 (ceiling 0, R-516)
i18n gate OK
==============================================================================
== gate: go-parity (i18n_go_parity.py)
==============================================================================
go-parity: base capture 736f54b49610 (7467 literals), 773 slice-2 keys listed, 272 pre-existing, 776 named in Go, 585 helper calls judged for arity
go-parity gate OK: every Go-side key carries base-commit text, byte for byte, and every judged call passes as many arguments as its message has verbs.
==============================================================================
== gate: gofmt (gofmt_gate.py)
==============================================================================
gofmt gate OK — every Go file under cmd, internal is gofmt-clean
==============================================================================
== gate: golden-notice (golden_notice.py /mnt/5_hdd/felhom.eu/wt/b3-gates)
==============================================================================
==============================================================================
NOTICE — v0.324.0 OWES A GOLDEN. (this NEVER blocks; see the docstring)
==============================================================================
newest released controller : 0.324.0 (this repo's CHANGELOG.md)
newest golden baked : 0.318.0 (felhom.eu documentation/tests/)
A machine installed right now would receive 0.318.0, not 0.324.0.
This is a REMINDER AT THE ONE MOMENT IT IS USEFUL — you are in the repo where the
release happens. It does not block, and must not: at the moment a release is
committed the golden cannot exist yet.
WHAT CLEARS IT: bake a golden (felhom.eu documentation/runbooks/RUNBOOK-manual-build.md
section 4.1), then vouch it — a THREE-field change: golden_version + agent_version +
min_agent. The bake record lands in felhom.eu documentation/tests/golden-<ver>-<date>/.
If this release deliberately needs no golden, record a waiver row in
felhom.eu documentation/backlog/OPEN-ITEMS.md — never a habit of bypassing.
==============================================================================
==============================================================================
== summary
==============================================================================
template-id OK (exit 0)
emoji OK (exit 0)
native-confirm OK (exit 0)
offbox-rename OK (exit 0)
app-row-dedup OK (exit 0)
mojibake OK (exit 0)
docker-v OK (exit 0)
secret-markup OK (exit 0)
retrieval-promise OK (exit 0)
debug-routes OK (exit 0)
reuse-refs OK (exit 0)
instructions OK (exit 0)
observations OK (exit 0)
minagent-header OK (exit 0)
i18n OK (exit 0)
go-parity OK (exit 0)
gofmt OK (exit 0)
golden-notice ADVISORY (exit 0, advisory)
all controller gates OK
@@ -0,0 +1,9 @@
--- FAIL: TestRenderSambaCompose_ConfigHashLabel (0.00s)
samba_test.go:125: a same-path rename left the compose byte-identical — compose up -d will not recreate (R-1109):
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/infra 0.007s
--- FAIL: TestSambaReconcile_SamePathRenameChangesCompose (0.00s)
samba_test.go:204: a same-path rename left docker-compose.yml unchanged — the running container is never recreated (R-1109)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.018s
FAIL
@@ -0,0 +1,10 @@
--- FAIL: TestNoBareDrivePathToPathHelpers (0.35s)
userdata_bare_drive_guard_test.go:124: internal/backup/offbox.go:1307 (discoverOffboxUnit): nr — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
userdata_bare_drive_guard_test.go:124: internal/backup/offbox_placement.go:168 (RecordedUnitForStack): root — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
userdata_bare_drive_guard_test.go:124: internal/backup/removed_units.go:125 (ListRemovedAppUnits): root — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
userdata_bare_drive_guard_test.go:124: internal/backup/removed_units.go:134 (ListRemovedAppUnits): root — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
userdata_bare_drive_guard_test.go:124: internal/stacks/delete.go:913 (GetStackBackupData): drivePath — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
userdata_bare_drive_guard_test.go:124: internal/web/handlers.go:3434 (appDetailsForPath): storagePath — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/appbackup 0.680s
FAIL
@@ -0,0 +1,32 @@
1 CENSUS 42
1 internal/api/router.go:removeStack
1 internal/appbackup/paths.go:AppDBDumpPath
1 internal/appbackup/paths.go:AppVolumeDumpPath
1 internal/appbackup/paths.go:RecoveryUnitComposePath
1 internal/appbackup/paths.go:RecoveryUnitManifestPath
1 internal/backup/appbackup_bridge.go:AppDataDir
1 internal/backup/appbackup_bridge.go:PrimaryBackupPath
1 internal/backup/appbackup_bridge.go:RecoveryUnitPath
1 internal/backup/backup.go:DumpAppVolumes
2 internal/backup/backup.go:pruneStalePrimaryDirs
1 internal/backup/backup.go:runDBDumpsInternal
1 internal/backup/kept_load.go:PrimaryUnitHome
1 internal/backup/offbox.go:discoverOffboxUnit
1 internal/backup/offbox_placement.go:RecordedUnitForStack
2 internal/backup/recovery_unit.go:captureRecoveryUnit
2 internal/backup/removed_units.go:ListRemovedAppUnits
1 internal/backup/restore_points.go:ListRestorePoints
1 internal/backup/restore_unit.go:primaryUnitDirFor
1 internal/backup/tier2.go:RunTier2
1 internal/backup/tier2.go:Tier2Info
1 internal/backup/tier2_capture.go:tier2CaptureSet
1 internal/backup/tier2_restore.go:rehydratePrimaryUnit
1 internal/backup/update_guard.go:RunAppBackupNow
1 internal/stacks/delete.go:GetStackBackupData
1 internal/stacks/migrate.go:appDataSkipSet
2 internal/stacks/migrate.go:migCleanup
4 internal/stacks/migrate.go:migCopy
2 internal/stacks/migrate.go:migSourceSize
1 internal/stacks/migrate.go:migValidate
4 internal/stacks/migrate.go:migVerify
1 internal/web/handlers.go:appDetailsForPath
@@ -0,0 +1,3 @@
--- FAIL: TestNoBareDrivePathToPathHelpers (0.27s)
userdata_bare_drive_guard_test.go:124: internal/backup/tier2_restore.go:253 (rehydratePrimaryUnit): drivePath — the first argument is not a namespace root by construction. Put a drive path through appbackup.NamespaceRootFor(drive, systemDataPath) first (R-203, R-1110).
FAIL
@@ -0,0 +1,5 @@
--- FAIL: TestR1112_StartConvertsTheFamilyFile (0.00s)
r1111_r1113_test.go:333: family.json still holds the session value after the start: {"members":[],"sessions":[{"id":"0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a6978","member":"","gen":0,"exp":"2026-11-06T07:48:09Z"}]}
FAIL
--- FAIL: TestR1112_MainOpensTheFamilyStore (0.01s)
r856_wiring_test.go:83: main.go does not call OpenFamilyStore — an old family.json keeps its session values until a family request
@@ -0,0 +1,7 @@
ok N1 the normal clone finds ../felhom.eu
ok F1 a fixture dir inside a repo keeps its own ../felhom.eu
ok G1 no git at all keeps ../felhom.eu
ok E1 FELHOM_SHARED_SCRIPTS wins
FAIL:
- W1 a worktree outside git/ finds the MAIN clone's sibling: resolved to /tmp/r1114-hbxrxu0g/wt/felhom.eu/scripts, want /tmp/r1114-hbxrxu0g/git/felhom.eu/scripts
@@ -0,0 +1,230 @@
== before: family.json sessions 2 household 2 keys ['exp', 'fp', 'gen', 'member']
== browser pass 1
{
"1_sign_in_lands_on": "/launcher",
"2_headers": {
"status": 200,
"xfo": "DENY",
},
"3_signout_control": {
"method": "post",
"hasCsrf": true
},
"4_after_signout": "/login",
"5_launcher_after_signout": "/login",
"6_sign_in_again": "/launcher",
"7_app metube-r1112.enkisfelhom.hu": {
"host": "metube-r1112.enkisfelhom.hu",
"path": "/",
"title": "MeTube",
"gatePage": false
},
"console": [
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
]
]
}
== after pass 1: family.json sessions 2 household 2 keys ['exp', 'fp', 'gen', 'member']
== browser pass 2
rm: cannot remove '<scratch>': Directory not empty
{
"1_sign_in_lands_on": "/launcher",
"2_headers": {
"status": 200,
"xfo": "DENY",
},
"3_signout_control": {
"method": "post",
"hasCsrf": true
},
"4_after_signout": "/login",
"5_launcher_after_signout": "/login",
"6_sign_in_again": "/launcher",
"7_app metube-r1112.enkisfelhom.hu": {
"host": "metube-r1112.enkisfelhom.hu",
"path": "/",
"title": "MeTube",
"gatePage": false
},
"console": [
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
]
]
}
== after pass 2: family.json sessions 2 household 2 keys ['exp', 'fp', 'gen', 'member']
== browser pass 3
{
"1_sign_in_lands_on": "/launcher",
"2_headers": {
"status": 200,
"xfo": "DENY",
},
"3_signout_control": {
"method": "post",
"hasCsrf": true
},
"4_after_signout": "/login",
"5_launcher_after_signout": "/login",
"6_sign_in_again": "/launcher",
"7_app metube-r1112.enkisfelhom.hu": {
"host": "metube-r1112.enkisfelhom.hu",
"path": "/",
"title": "MeTube",
"gatePage": false
},
"console": [
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/adventurelog-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/docmost-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/kimai-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/opengist-logo.svg"
],
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/privatebin-logo.svg"
]
]
}
== after pass 3: family.json sessions 2 household 2 keys ['exp', 'fp', 'gen', 'member']
10:00:55 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'userdata_kept': ['/m
10:01:04 [X] after remove: deployed=False leftovers='/opt/docker/stacks/metube'
remove: 200
/mnt/felhom-drives/hdd_1/userdata/media/metube
rmdir: failed to remove '/mnt/felhom-drives/hdd_1/userdata/media/metube/*': No such file or directory
ls: cannot access '/mnt/felhom-drives/hdd_1/userdata/media/metube': No such file or directory
@@ -0,0 +1,65 @@
09:44:00 POST /sharing/enable {'enabled': 'on', 'server_name': 'FELHOMHP'} -> HTTP/2 303 /sharing?flash=flash.sharing.saved_preparing
09:44:01 POST /sharing/password {'smb_password': '<redacted>', 'smb_password_confirm': '<redacted>'} -> HTTP/2 303 /sharing?flash=flash.sharing.pw_set_preparing
09:44:06 status: {'data': {'error': '', 'phase': 'running', 'running': True}, 'ok': True}
09:44:06 POST /sharing/shares {'name': 'r1109a', 'mode': 'new', 'storage_root': '/mnt/felhom-drives/hdd_1'} -> HTTP/2 303 /sharing?flash=flash.sharing.created
09:44:18 samba: created 2026-10-17T07:44:06.208053287Z label=d8c42627eb7f9b1856e142015f267c635968440e0a959ceaecaab30aac3854e8 status=running
smb.conf shares: [global] [beolvasas] [r1109a]
smb.conf hash: d8c42627eb7f9b18… compose binds: - /mnt/felhom-drives/hdd_1/shares/r1109a:/mnt/felhom-drives/hdd_1/shares/r1109a - /mnt/sys_drive/felhom-data/userdata/import:/mnt/sys_drive/felhom-data/userdata/import
inside the container: [global] [beolvasas] [r1109a]
09:44:24 read //r1109a from 9202:
Can't load /etc/samba/smb.conf - run testparm to debug it
putting file /tmp/p.txt as \probe-r1109a.txt (2.1 kB/s) (average 2.1 kB/s)
probe-r1109a.txt A 13 Sat Oct 17 07:44:23 2026
getting file \probe-r1109a.txt of size 13 as /tmp/b.txt (12.7 KiloBytes/sec) (average 12.7 KiloBytes/sec)
read back: NOTHING
wrong password: session setup failed: NT_STATUS_LOGON_FAILURE
09:44:54 POST /sharing/shares/delete {'name': 'r1109a'} -> HTTP/2 303 /sharing?flash=flash.sharing.deleted
09:45:00 POST /sharing/shares {'name': 'r1109b', 'mode': 'existing', 'path': '/mnt/felhom-drives/hdd_1/shares/r1109a'} -> HTTP/2 303 /sharing?flash=flash.sharing.created
09:45:14 samba: created 2026-10-17T07:45:00.460658307Z label=22b002052dc48031f97c563d829abdef76fdf67ed517d623967c9a253d0fb8d9 status=running
smb.conf shares: [global] [beolvasas] [r1109b]
smb.conf hash: 22b002052dc48031… compose binds: - /mnt/felhom-drives/hdd_1/shares/r1109a:/mnt/felhom-drives/hdd_1/shares/r1109a - /mnt/sys_drive/felhom-data/userdata/import:/mnt/sys_drive/felhom-data/userdata/import
inside the container: [global] [beolvasas] [r1109b]
09:45:18 read //r1109b from 9202:
Can't load /etc/samba/smb.conf - run testparm to debug it
putting file /tmp/p.txt as \probe-r1109b.txt (12.7 kB/s) (average 12.7 kB/s)
probe-r1109b.txt A 13 Sat Oct 17 07:45:17 2026
getting file \probe-r1109b.txt of size 13 as /tmp/b.txt (12.7 KiloBytes/sec) (average 12.7 KiloBytes/sec)
read back: r1109-r1109b
wrong password: session setup failed: NT_STATUS_LOGON_FAILURE
09:45:22 read //r1109a from 9202:
Can't load /etc/samba/smb.conf - run testparm to debug it
tree connect failed: NT_STATUS_BAD_NETWORK_NAME
read back: NOTHING
wrong password: session setup failed: NT_STATUS_LOGON_FAILURE
09:45:29 POST /sharing/shares {'name': 'r1109c', 'mode': 'existing', 'path': '/mnt/felhom-drives/hdd_1/shares/r1109a'} -> HTTP/2 303 /sharing?flash=flash.sharing.created
09:45:43 samba: created 2026-10-17T07:45:29.544095301Z label=60c24b9e1dc6d423f2babb4653569601eddd84e8b74b0ef1a577b666f10955b4 status=running
smb.conf shares: [global] [beolvasas] [r1109b] [r1109c]
smb.conf hash: 60c24b9e1dc6d423… compose binds: - /mnt/felhom-drives/hdd_1/shares/r1109a:/mnt/felhom-drives/hdd_1/shares/r1109a - /mnt/felhom-drives/hdd_1/shares/r1109a:/mnt/felhom-drives/hdd_1/shares/r1109a - /mnt/sys_drive/felhom-data/userdata/import:/mnt/sys_drive/felhom-data/userdata/import
inside the container: [global] [beolvasas] [r1109b] [r1109c]
09:45:46 controller log: 2026/10/17 07:44:00 samba.go:287: [INFO] [samba] household SMB password applied for user=felhom
2026/10/17 07:44:01 samba_ensure_job.go:189: [INFO] [sharing] samba ready after 0s
2026/10/17 07:44:06 samba.go:183: [INFO] [samba] applying samba stack: shares=2 config_changed=true
2026/10/17 07:44:53 samba.go:183: [INFO] [samba] applying samba stack: shares=1 config_changed=true
2026/10/17 07:45:00 samba.go:183: [INFO] [samba] applying samba stack: shares=2 config_changed=true
2026/10/17 07:45:29 samba.go:183: [INFO] [samba] applying samba stack: shares=3 config_changed=true
09:45:50 read //r1109c from 9202:
Can't load /etc/samba/smb.conf - run testparm to debug it
putting file /tmp/p.txt as \probe-r1109c.txt (12.7 kB/s) (average 12.7 kB/s)
probe-r1109c.txt A 13 Sat Oct 17 07:45:49 2026
getting file \probe-r1109c.txt of size 13 as /tmp/b.txt (130000.0 KiloBytes/sec) (average inf KiloBytes/sec)
read back: r1109-r1109c
wrong password: session setup failed: NT_STATUS_LOGON_FAILURE
09:45:54 read //r1109b from 9202:
Can't load /etc/samba/smb.conf - run testparm to debug it
putting file /tmp/p.txt as \probe-r1109b.txt (12.7 kB/s) (average 12.7 kB/s)
probe-r1109b.txt A 13 Sat Oct 17 07:45:53 2026
getting file \probe-r1109b.txt of size 13 as /tmp/b.txt (130000.0 KiloBytes/sec) (average inf KiloBytes/sec)
read back: r1109-r1109b
wrong password: session setup failed: NT_STATUS_LOGON_FAILURE
09:46:07 POST /sharing/password {'smb_password': '<redacted>', 'smb_password_confirm': '<redacted>'} -> HTTP/2 303 /sharing?flash=flash.sharing.pw_set_preparing
09:46:13 POST /sharing/shares/delete {'name': 'r1109b'} -> HTTP/2 303 /sharing?flash=flash.sharing.deleted
09:46:13 POST /sharing/shares/delete {'name': 'r1109c'} -> HTTP/2 303 /sharing?flash=flash.sharing.deleted
09:46:13 POST /sharing/enable {'enabled': 'off', 'server_name': 'FELHOMHP'} -> HTTP/2 303 /sharing?flash=flash.sharing.disabled
09:46:27 teardown: ls: cannot access '/mnt/felhom-drives/hdd_1/shares': No such file or directory
@@ -0,0 +1,139 @@
08:55:58 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
08:57:38 [1] deployed, controller state=running, pinned={'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:18-alpine', 'docmost-redis': 'redis:7-alpine'}
08:57:38 install docmost: True
08:57:38 state=running pinned={'docmost': 'docmost/docmost:0.96.0', 'docmost-postgres': 'postgres:18-alpine', 'docmost-redis': 'redis:7-alpine'}
08:57:50 seed: 100/100 SET OK; expected set sha256=6531ad3781df1b1091d075c7573811355c4cad895618fc36be1443762c9f0262; dbsize now 141
08:57:53 app's own keys (non-probe): 41 (approx, scan)
08:58:09 check after-seed: 100/100 probe keys returned with their value; dbsize=141
08:58:09 INFO persistence: loading:0, rdb_changes_since_last_save:310, rdb_last_save_time:1792220233, rdb_last_bgsave_status:ok, rdb_saves:0, aof_enabled:1, aof_last_write_status:ok
08:58:09 container: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 48 seconds (healthy) |
08:58:09 /data: /data:
total 12
drwxr-xr-x 3 redis redis 4096 Oct 17 08:57 .
drwxr-xr-x 1 root root 4096 Oct 17 08:57 ..
drwx------ 2 redis redis 4096 Oct 17 08:57 appendonlydir
/data/appendonlydir:
total 52
drwx------ 2 redis redis 4096 Oct 17 08:57 .
drwxr-xr-x 3 redis redis 4096 Oct 17 08:57 ..
-rw------- 1 redis redis 89 Oct 17 08:57 appendonly.aof.1.base.rdb
-rw------- 1 redis redis 32398 Oct 17 08:58 appendonly.aof.1.incr.aof
-rw------- 1 redis redis 88 Oct 17 08:57 appendonly.aof.manifest
08:58:34 sync: redis before: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up About a minute (healthy) |
08:58:48 +14s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up About a minute (healthy) |
09:00:50 +136s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 3 minutes (healthy) |
09:02:53 +259s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 5 minutes (healthy) |
09:04:56 +382s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 7 minutes (healthy) |
09:07:00 +506s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 9 minutes (healthy) |
09:09:05 +631s waiting: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 11 minutes (healthy) |
09:10:14 restart: compose files on the box: /opt/docker/stacks/docmost/docker-compose.yml: command: redis-server --appendonly no | /opt/docker/stacks/docmost/applied-compose.yml: command: redis-server --appendonly no |
09:10:24 redis before: 2026-10-17T06:57:11.089901155Z ["redis-server","--appendonly","yes"] | Up 13 minutes (healthy) |
09:10:36 POST /api/stacks/docmost/restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack docmost restart requested — state now: starting'}
09:11:15 stack: state=running; redis after: 2026-10-17T07:10:24.527949447Z ["redis-server","--appendonly","no"] | Up 46 seconds (healthy) |
09:11:19 redis log: 1:M 17 Oct 2026 09:10:25.520 * Loading RDB produced by version 7.4.11 | 1:M 17 Oct 2026 09:10:25.520 * RDB age 1 seconds | 1:M 17 Oct 2026 09:10:25.520 * RDB memory usage when created 2.57 Mb | 1:M 17 Oct 2026 09:10:25.520 * Done loading RDB, keys loaded: 133, keys expired: 0. | 1:M 17 Oct 2026 09:10:25.521 * DB loaded from disk: 0.001 seconds | 1:M 17 Oct 2026 09:10:25.521 * Ready to accept connections tcp |
09:11:35 check after-restart: 100/100 probe keys returned with their value; dbsize=141
09:11:35 INFO persistence: loading:0, rdb_changes_since_last_save:266, rdb_last_save_time:1792221025, rdb_last_bgsave_status:ok, rdb_saves:0, aof_enabled:0, aof_last_write_status:ok
09:11:35 container: 2026-10-17T07:10:24.527949447Z ["redis-server","--appendonly","no"] | Up About a minute (healthy) |
09:11:35 /data: /data:
total 28
drwxr-xr-x 3 redis redis 4096 Oct 17 09:10 .
drwxr-xr-x 1 root root 4096 Oct 17 09:10 ..
drwx------ 2 redis redis 4096 Oct 17 08:57 appendonlydir
-rw------- 1 redis redis 14378 Oct 17 09:10 dump.rdb
/data/appendonlydir:
total 352
drwx------ 2 redis redis 4096 Oct 17 08:57 .
drwxr-xr-x 3 redis redis 4096 Oct 17 09:10 ..
-rw------- 1 redis redis 89 Oct 17 08:57 appendonly.aof.1.base.rdb
-rw------- 1 redis redis 337696 Oct 17 09:10 appendonly.aof.1.incr.aof
-rw------- 1 redis redis 88 Oct 17 08:57 appendonly.aof.manifest
09:13:02 backup: POST /api/backup/run -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:14:27 +85s 2026/10/17 07:14:12 backup.go:707: [INFO] [backup] App-data backup completed: 3 databases (51.6 MB total), 4 volume dump(s) (1m10.829s)
09:14:30 volume dumps: 2026/10/17 07:13:08 backup.go:1019: [INFO] [backup] Restarting docmost after volume dump
2026/10/17 07:13:08 manager.go:1332: [DEBUG] [stacks] StartStack docmost: current state=stopped deployed=true
2026/10/17 07:13:08 manager.go:1335: [INFO] [stacks] Starting stack: docmost
2026/10/17 07:13:08 manager.go:1342: [DEBUG] [stacks] StartStack docmost: prepared 10 env vars for compose
2026/10/17 07:13:14 manager.go:906: [DEBUG] [stacks] restart-policy of down member "docmost" = "unless-stopped"
2026/10/17 07:13:19 manager.go:1350: [INFO] [stacks] Stack docmost started successfully (took 11.5s)
2026/10/17 07:13:23 manager.go:1786: [INFO] [stacks] Stack docmost post-start status:
2026/10/17 07:13:23 manager.go:1789: [INFO] [stacks] docmost docmost/docmost:0.96.0@sha256:b56947fcfd08aab8fae12a377e1792784786adbf8b96e4281f14ef4fc072685a running Up 3 seconds (health: starting)
09:14:33 units:
dir=
total 64
drwxr-xr-x 7 root root 4096 06:58:19 .
drwxr-xr-x 20 root root 4096 02:52:46 ..
-rw-r--r-- 1 root root 0 07:51:38 .bash_history
-rw-r--r-- 1 root root 607 09:05:00 .bashrc
drwx------ 3 root root 4096 18:14:32 .docker
-rw-r--r-- 1 root root 132 09:05:00 .profile
drwx------ 2 root root 4096 19:32:40 .ssh
-rw-r--r-- 1 root root 2560 14:00:44 S10-box-fresh-768-noswap.txt
-rw-r--r-- 1 root root 2358 13:28:40 S2-box-positive.txt
-rw------- 1 root root 1944 12:39:10 controller.yaml.pre-lock-probe
-rw-r--r-- 1 root root 137 07:19:40 daemon.json.bak-2026-10-04
-rwxr-xr-x 1 root root 1779 13:44:59 pgsample.sh
drwxr-xr-x 3 root root 4096 07:12:40 r1096
-rw-r--r-- 1 root root 218 18:09:33 r626-events.txt
-rw-r--r-- 1 root root 999 19:53:14 repoint-night1005.sh
drwx------ 6 root root 4096 06:31:38 shots
drwxr-xr-x 3 root root 4096 14:07:29 sizing
total 68588
drwxr-xr-x 2 root root 4096 07:13:08 .
drwxr-xr-x 5 root root 4096 07:14:12 ..
-rw-r--r-- 1 root root 69860352 07:13:06 docmost_docmost_postgres_data.tar
-rw-r--r-- 1 root root 359424 07:13:08 docmost_docmost_redis_data.tar
-rw-r--r-- 1 root root 1536 07:13:06 docmost_docmost_storage.tar
drwxr-xr-x 999/1000 0 2026-10-17 07:13 ./
drwx------ 999/1000 0 2026-10-17 06:57 ./appendonlydir/
-rw------- 999/1000 88 2026-10-17 06:57 ./appendonlydir/appendonly.aof.manifest
-rw------- 999/1000 337696 2026-10-17 07:10 ./appendonlydir/appendonly.aof.1.incr.aof
-rw------- 999/1000 89 2026-10-17 06:57 ./appendonlydir/appendonly.aof.1.base.rdb
-rw------- 999/1000 16193 2026-10-17 07:13 ./dump.rdb
09:15:06 flush: FLUSHALL -> OK; dbsize 0
09:15:22 check after-flush: 0/100 probe keys returned with their value; dbsize=0
09:15:22 INFO persistence: loading:0, rdb_changes_since_last_save:0, rdb_last_save_time:1792221302, rdb_last_bgsave_status:ok, rdb_saves:0, aof_enabled:0, aof_last_write_status:ok
09:15:22 container: 2026-10-17T07:13:08.586215064Z ["redis-server","--appendonly","no"] | Up 2 minutes (healthy) |
09:15:22 /data: /data:
total 16
drwxr-xr-x 3 redis redis 4096 Oct 17 09:15 .
drwxr-xr-x 1 root root 4096 Oct 17 09:13 ..
drwx------ 2 redis redis 4096 Oct 17 08:57 appendonlydir
-rw------- 1 redis redis 89 Oct 17 09:15 dump.rdb
/data/appendonlydir:
total 352
drwx------ 2 redis redis 4096 Oct 17 08:57 .
drwxr-xr-x 3 redis redis 4096 Oct 17 09:15 ..
-rw------- 1 redis redis 89 Oct 17 08:57 appendonly.aof.1.base.rdb
-rw------- 1 redis redis 337696 Oct 17 09:10 appendonly.aof.1.incr.aof
-rw------- 1 redis redis 88 Oct 17 08:57 appendonly.aof.manifest
09:15:23 [R] restoring docmost from snapshot 'helyi' (of 1 offered)
09:15:23 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:15:23 + 0.0s restore (True, None, None)
09:15:53 + 30.4s restore (False, None, None)
09:15:53 [R] after restore: state=running hold=None phase=None
09:15:57 restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "location": ["location: /backups/restore?flash=flash.restore.started"], "seconds": 30.4}
09:16:33 check after-restore: 100/100 probe keys returned with their value; dbsize=141
09:16:33 INFO persistence: loading:0, rdb_changes_since_last_save:266, rdb_last_save_time:1792221328, rdb_last_bgsave_status:ok, rdb_saves:0, aof_enabled:0, aof_last_write_status:ok
09:16:33 container: 2026-10-17T07:15:28.643263028Z ["redis-server","--appendonly","no"] | Up 57 seconds (healthy) |
09:16:33 /data: /data:
total 28
drwxr-xr-x 3 redis redis 4096 Oct 17 09:15 .
drwxr-xr-x 1 root root 4096 Oct 17 09:15 ..
drwx------ 2 redis redis 4096 Oct 17 09:15 appendonlydir
-rw------- 1 redis redis 16193 Oct 17 09:13 dump.rdb
/data/appendonlydir:
total 348
drwx------ 2 redis redis 4096 Oct 17 09:15 .
drwxr-xr-x 3 redis redis 4096 Oct 17 09:15 ..
-rw------- 1 redis redis 89 Oct 17 08:57 appendonly.aof.1.base.rdb
-rw------- 1 redis redis 337696 Oct 17 09:10 appendonly.aof.1.incr.aof
-rw------- 1 redis redis 88 Oct 17 08:57 appendonly.aof.manifest
09:16:44 [X] stop -> 200 {'ok': True, 'message': 'Stack docmost stop completed'}
09:17:16 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'docmost', 'volumes_removed': ['docmost_docmost_postgres_data', 'docmost_docmost_redis_data', 'docmost_docmost_storage'], 'hdd_
09:17:24 [X] after remove: deployed=False leftovers='/opt/docker/stacks/docmost'
09:17:24 remove: 200
@@ -0,0 +1,49 @@
== A1 CONTROL: the old-form copy, undamaged, --appendonly yes
state: running exit=0
dbsize: 129 probe key 7: v7-7c5…
log: 1:M 17 Oct 2026 07:12:20.835 * DB loaded from base file appendonly.aof.1.base.rdb: 0.000 seconds
log: 1:M 17 Oct 2026 07:12:20.836 * DB loaded from incr file appendonly.aof.1.incr.aof: 0.001 seconds
log: 1:M 17 Oct 2026 07:12:20.836 * DB loaded from append only file: 0.001 seconds
log: 1:M 17 Oct 2026 07:12:20.836 * Ready to accept connections tcp
== A2 the old form, damaged the 2026-09-24 way: 64 zero bytes written INTO the AOF, 400 bytes before its end
file 35789 bytes, zeros at 35389..35452; size after 35789
state: exited exit=1
dbsize: Error response from daemon: container 5abf3c47a6d2e027686392724cfe7b5fb4b76c42db45439d70ec283975512e25 is not running probe key 7: Error …
log: 1:C 17 Oct 2026 07:12:25.564 * Configuration loaded
log: 1:M 17 Oct 2026 07:12:25.567 * Done loading RDB, keys loaded: 0, keys expired: 0.
log: 1:M 17 Oct 2026 07:12:25.567 * DB loaded from base file appendonly.aof.1.base.rdb: 0.000 seconds
log: 1:M 17 Oct 2026 07:12:25.568 # Bad file format reading the append only file appendonly.aof.1.incr.aof: make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>
== R1 the NEW form on a FULL disk (tmpfs 1 MiB): a save that cannot be written
OK
0b04403f53805a16d01d10b490b70bcc7045c381dedd834acf449317f5f33d74 /data/r/dump.rdb
total 4
drwxr-xr-x 2 redis root 60 Oct 17 07:12 .
drwxr-xr-x 3 redis redis 80 Oct 17 07:12 ..
-rw-r--r-- 1 redis redis 1126 Oct 17 07:12 dump.rdb
now 1.2 MB of new data, which cannot fit:
Background saving started
rdb_changes_since_last_save:1
rdb_last_bgsave_status:err
MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.
0b04403f53805a16d01d10b490b70bcc7045c381dedd834acf449317f5f33d74 /data/r/dump.rdb
total 4
drwxr-xr-x 2 redis root 60 Oct 17 07:12 .
drwxr-xr-x 3 redis redis 80 Oct 17 07:12 ..
-rw-r--r-- 1 redis redis 1126 Oct 17 07:12 dump.rdb
tmpfs 1024 8 1016 1% /data
16:M 17 Oct 2026 07:12:31.495 * Background saving started by pid 91
91:C 17 Oct 2026 07:12:31.506 # Write error while saving DB to the disk(rdbSaveRio): No space left on device
16:M 17 Oct 2026 07:12:31.560 # Background saving error
stop WITHOUT saving (what a kill does), start again on the same folder:
dbsize after start: 50 probe 7: v7
109:M 17 Oct 2026 07:12:34.604 * Done loading RDB, keys loaded: 50, keys expired: 0.
== R2 what a TRUNCATED dump.rdb does (half the file) — the shape the temp-file-and-rename write never leaves behind
dump.rdb cut from 14378 to 7189 bytes
state: exited exit=1
dbsize: Error response from daemon: container 5d3c6d322f4553552f6fdae5e1eea896a8fb9ab9b42a364b7fd0b03099ae9e90 is not running probe key 7: Error …
log: 1:M 17 Oct 2026 07:12:36.127 # Internal error in RDB reading offset 0, function at rdb.c:2818 -> Stream listpacks loading failed.
log: --- RDB ERROR DETECTED ---
log: [offset 6493] Internal error in RDB reading offset 0, function at rdb.c:2818 -> Stream listpacks loading failed.
log: [info] 2 already expired
== cleanup: 0 r1096-* containers left
@@ -0,0 +1,27 @@
26: repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
27: sync_interval: 15m
-rw------- 1 root root 1944 Oct 12 20:52 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-redis1017
mirror main daff3d2 DRILL docmost: redis AOF -> RDB (--appendonly no), R-1096 proof on 9202
cache HEAD 33a4b3b The copy gate's credential case convicts again; the decoy suite is a gate (R-1115)
https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
0
/opt/docker/stacks/docmost
immich-server Up 3 minutes (healthy)
immich-postgres Up 4 minutes (healthy)
immich-machine-learning Up 4 minutes (healthy)
immich-redis Up 4 minutes (healthy)
filebrowser Up 22 minutes (healthy)
felhom-controller Up 19 seconds (healthy)
traefik Up 28 hours
paperless-webserver Up 4 minutes (healthy)
paperless-redis Up 4 minutes (healthy)
paperless-postgres Up 4 minutes (healthy)
immich-server Up 4 minutes (healthy)
immich-postgres Up 4 minutes (healthy)
immich-machine-learning Up 4 minutes (healthy)
immich-redis Up 4 minutes (healthy)
filebrowser Up 23 minutes (healthy)
felhom-controller Up 51 seconds (healthy)
traefik Up 28 hours
@@ -0,0 +1,34 @@
09:31:28 POST /api/sync -> 200 {'ok': True, 'data': {'ok': True, 'updated': ['docmost', 'immich', 'nextcloud', 'paperless-ngx', 'romm'], 'message': 'Sablonok frissítve — frissítve: docmost, immich, nextcloud, paperless-ngx, romm',
09:31:36 == romm before:
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T02:43:43.44632381Z ["redis-server","--appendonly","yes"] Up 5 hours (healthy)
dbsize: 153 rdb_last_bgsave_status:ok aof_enabled:1
/data: appendonlydir dump.rdb
09:31:53 POST /api/stacks/romm/restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack romm restart requested — state now: starting'}
09:32:52 == romm after (state=running hold=None):
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T07:31:37.095549596Z ["redis-server","--appendonly","no"] Up About a minute (healthy)
dbsize: 150 rdb_last_bgsave_status:ok aof_enabled:0
/data: appendonlydir dump.rdb
09:32:56 == docmost before:
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T02:43:10.245018058Z ["redis-server","--appendonly","yes"] Up 5 hours (healthy)
dbsize: 54 rdb_last_bgsave_status:ok aof_enabled:1
/data: appendonlydir dump.rdb
09:33:08 POST /api/stacks/docmost/restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack docmost restart requested — state now: starting'}
09:33:42 == docmost after (state=running hold=None):
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T07:32:56.51476244Z ["redis-server","--appendonly","no"] Up 43 seconds (healthy)
dbsize: 58 rdb_last_bgsave_status:ok aof_enabled:0
/data: appendonlydir dump.rdb
09:33:45 == paperless-ngx before:
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T02:43:27.394575235Z ["redis-server","--appendonly","yes"] Up 5 hours (healthy)
dbsize: 2 rdb_last_bgsave_status:ok aof_enabled:1
/data: appendonlydir dump.rdb
09:34:06 POST /api/stacks/paperless-ngx/restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack paperless-ngx restart requested — state now: starting'}
09:35:00 == paperless-ngx after (state=running hold=None):
compose: command: redis-server --appendonly no| command: redis-server --appendonly no|
container: 2026-10-17T07:33:45.874328224Z ["redis-server","--appendonly","no"] Up About a minute (healthy)
dbsize: 4 rdb_last_bgsave_status:ok aof_enabled:0
/data: appendonlydir dump.rdb
@@ -0,0 +1,2 @@
/opt/docker/stacks/paperless-ngx/docker-compose.yml: command: redis-server --appendonly no
/opt/docker/stacks/immich/docker-compose.yml: command: redis-server --appendonly no
@@ -0,0 +1,5 @@
read 2026-10-17T07:31:00Z integrity: ok
Tester-2 report 2026-10-04 18:06:24: none of the five
demo-felhom report 2026-10-17 07:21:53: none of the five
demo-hp report 2026-10-17 07:21:53: ['romm', 'docmost', 'paperless-ngx']
tester-1 report 2026-10-17 07:21:58: ['paperless-ngx']
@@ -0,0 +1,4 @@
Tester-2 report 2026-10-04 18:06:24: of the five -> none (deployed 0)
demo-felhom report 2026-10-17 07:06:53: of the five -> none (deployed 5)
demo-hp report 2026-10-17 07:06:53: of the five -> ['romm', 'docmost', 'paperless-ngx'] (deployed 12)
tester-1 report 2026-10-17 07:06:58: of the five -> ['paperless-ngx'] (deployed 10)
@@ -0,0 +1,2 @@
/opt/docker/stacks/paperless-ngx/docker-compose.yml: command: redis-server --appendonly yes
/opt/docker/stacks/paperless-ngx/applied-compose.yml: command: redis-server --appendonly yes
@@ -0,0 +1,295 @@
ok FACT: docmost-postgres major + 1 (read from the clone) rc=1 (expected 1)
ok FACT: docmost-postgres major + 1, alone rc=1 (expected 1)
ok FACT: kimai-db 11.6 -> 12.3 BUNDLED with the kimai app bump rc=1 (expected 1)
ok FACT: kimai-db mariadb:11.6 -> mariadb:lts (major unreadable) rc=2 (expected 2)
ok GENUINE: kimai-db mariadb:11.x -> 11.99 (within major) rc=0 (expected 0)
ok GENUINE: kimai-db mariadb:11.6 -> 12.3 ALONE (the R-469 lift) rc=0 (expected 0)
ok GENUINE: docmost-postgres major+1 ALONE with its proven two-venue MARKED entry rc=0 (expected 0)
ok DECOY: the entry is proven on both venues but carries NO conversion mark rc=1 (expected 1)
ok DECOY: the marked entry cites ONE venue only (no box_evidence) rc=1 (expected 1)
ok DECOY: the mark sits in a COMMENT, the entry has none rc=1 (expected 1)
ok DECOY: the marked entry, but the move is BUNDLED with the app's own bump rc=1 (expected 1)
ok FACT: adventurelog's postgis 16 -> 17 (the postgis family was never judged before) rc=1 (expected 1)
ok DECOY: major moves only in a comment + serverVersion env rc=0 (expected 0)
ok DECOY: the APP image crosses a major (kimai 2.57 -> 3.0) rc=0 (expected 0)
ok DECOY: 'mariadb:12.3' lands in README.md, not a template rc=0 (expected 0)
ok FACT: kimai image moves, catalog_since untouched rc=1 (expected 1)
ok FACT: kimai image moves, catalog_since set to a FUTURE year rc=1 (expected 1)
ok GENUINE: kimai image moves AND catalog_since = today rc=0 (expected 0)
ok DECOY: image moves; today's date lands in a COMMENT and README, the field stays rc=1 (expected 1)
ok DECOY: only a comment + env line change, images untouched, date untouched rc=0 (expected 0)
ok FACT: a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT: an em dash in a Hungarian string (R-932) rc=1 (expected 1)
ok FACT: a Hungarian first_step removed rc=1 (expected 1)
ok FACT: a NEW app is not in the freeze rc=1 (expected 1)
ok FACT: an unknown key inside the English block rc=1 (expected 1)
ok FACT: an English deploy field with no Hungarian twin rc=1 (expected 1)
ok FACT: an accented Hungarian letter left in the English rc=1 (expected 1)
ok FACT: ASCII-only Hungarian left in the English (no accent to find) rc=1 (expected 1)
ok FACT: the product begs ("please") rc=1 (expected 1)
ok FACT: an English retrieval promise the Hungarian never made rc=1 (expected 1)
ok GENUINE: a REGISTERED true retrieval promise passes rc=0 (expected 0)
ok FACT: registered for ANOTHER app - the promise still convicts, the entry is stale rc=1 (expected 1)
ok FACT: registered on the path, but the sentence was rewritten (match gone) rc=1 (expected 1)
ok FACT: a STALE entry - nothing in the English promises it any more rc=1 (expected 1)
ok FACT: a registered promise with a two-word reason rc=1 (expected 1)
ok FACT: a credential token rewritten in translation rc=1 (expected 1)
ok FACT: a credential value reworded in translation (one digit added) rc=1 (expected 1)
ok FACT: an i18n block for a language the controller does not render rc=1 (expected 1)
ok FACT: an English list with a different number of steps rc=1 (expected 1)
ok FACT: ratchet — fewer strings translated than the ceiling records rc=1 (expected 1)
ok FACT: ratchet — more translated than the ceiling records rc=1 (expected 1)
ok GENUINE: a correct English block on privatebin rc=0 (expected 0)
ok GENUINE: naming an app does not change the coverage count rc=0 (expected 0)
ok FACT(no-yaml): a Hungarian byte changed in a frozen string rc=1 (expected 1)
ok FACT(no-yaml): a frozen Hungarian line deleted rc=1 (expected 1)
ok FACT(no-yaml): a NEW app is not in the freeze rc=1 (expected 1)
ok GENUINE(no-yaml): the untouched tree passes, and SAYS what it did not check rc=0 (expected 0)
ok GENUINE(no-yaml): romm's escaped-quote help_texts are still found rc=0 (expected 0)
ok DECOY: Hungarian rewritten inside a YAML COMMENT rc=0 (expected 0)
ok DECOY: a frozen Hungarian sentence pasted into README.md rc=0 (expected 0)
ok DECOY: display_name changed - a NAME, never copy rc=0 (expected 0)
ok DECOY: docs_url changed - configuration, never copy rc=0 (expected 0)
ok DECOY: Hungarian text added to a docker-compose.yml rc=0 (expected 0)
-- probe-matches-compose: the facts (each MUST be refused)
ok FACT: tandoor's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: wger's real R-618 port fault, re-introduced rc=1 (expected 1)
ok FACT: zipline's real R-618 path fault, re-introduced rc=1 (expected 1)
ok GENUINE: the fixed tree passes rc=0 (expected 0)
ok FACT: a clean app given a wrong probe port rc=1 (expected 1)
ok FACT: api+expect probe given a path the app does not answer rc=1 (expected 1)
-- probe-matches-compose: the decoys (the label moves, the fact does not)
ok DECOY: the port moves in a COMMENT in .felhom.yml rc=0 (expected 0)
ok DECOY: traefik loadbalancer.server.port changed rc=0 (expected 0)
ok DECOY: a published `ports:` mapping changed rc=0 (expected 0)
ok DECOY: a NON-probed sidecar's healthcheck port changed rc=0 (expected 0)
ok DECOY: path mismatch on a probe that CANNOT fail on it -> WARN, not FAIL rc=0 (expected 0)
-- probe-matches-compose: the DEGRADED mode CI actually runs (no PyYAML)
ok DEGRADED: the fixed tree passes, and SAYS it is degraded rc=0 (expected 0)
ok DEGRADED FACT: tandoor's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: wger's port fault still refused rc=1 (expected 1)
ok DEGRADED FACT: zipline's path fault still refused rc=1 (expected 1)
ok DEGRADED DECOY: traefik label moves, the fact does not rc=0 (expected 0)
-- probe-matches-compose: the probe TARGET (R-630)
ok FACT: paperless-ngx without its explicit container is refused rc=1 (expected 1)
ok FACT: immich's FOUR prefix candidates are an ambiguity, not a pick rc=1 (expected 1)
ok FACT: an explicit container no service declares is refused rc=1 (expected 1)
ok GENUINE: both explicit containers resolve rc=0 (expected 0)
ok DECOY: the container name moves in a COMMENT, not the field rc=0 (expected 0)
ok DECOY: a UNIQUE prefix still resolves without the field rc=0 (expected 0)
ok DEGRADED: the explicit container is read without PyYAML too rc=0 (expected 0)
ok DEGRADED FACT: removing it still refuses rc=1 (expected 1)
-- test-record-move: an image move needs its own proven record
ok FACT: a bare image move, no entry rc=1 (expected 1)
ok GENUINE: a proven entry whose digest the registry serves rc=0 (expected 0)
ok FACT: the entry's verdict is failed rc=1 (expected 1)
ok FACT: the registry now serves another digest rc=1 (expected 1)
ok INCONCLUSIVE: the registry cannot be asked rc=2 (expected 2)
ok DECOY: the entry only in a COMMENT under update_ladder rc=1 (expected 1)
ok DECOY: the entry only in README.md rc=1 (expected 1)
ok FACT: a new move carrying a BACKFILLED entry rc=1 (expected 1)
ok FACT: memory_tight and the limit did not move rc=1 (expected 1)
ok GENUINE: memory_tight WITH the limit raised rc=0 (expected 0)
ok DECOY: a ref moves in a compose COMMENT only rc=0 (expected 0)
-- test-record (static): the newest step IS the compose
ok GENUINE: a ladder whose head is the compose rc=0 (expected 0)
ok FACT: the compose moved past the ladder's head rc=1 (expected 1)
ok FACT: a line that is not one JSON entry rc=1 (expected 1)
ok FACT: a gap between steps rc=1 (expected 1)
ok FACT: a failed verdict sits in the ladder rc=1 (expected 1)
-- test-record (static): every intermediate step carries its own definition
ok FACT: a two-step ladder with NO steps/ file for the first step rc=1 (expected 1)
ok GENUINE: a two-step ladder whose first step carries its definition rc=0 (expected 0)
ok DECOY: the steps/ file has the right NAME and names the head's image rc=1 (expected 1)
ok DECOY: the step's definition sits beside the template under another name rc=1 (expected 1)
ok FACT: the step has its compose but no .felhom.yml rc=1 (expected 1)
ok DECOY: the step's .felhom.yml exists by NAME and holds no healthcheck rc=1 (expected 1)
-- test-record: a same-tag re-test (decision 52)
ok GENUINE: head + a re-test from its digest, both venues rc=0 (expected 0)
ok FACT: a re-test with NO new digest rc=1 (expected 1)
ok FACT: a re-test without the box venue rc=1 (expected 1)
ok FACT: a re-test FROM a digest the previous entry never tested rc=1 (expected 1)
ok DECOY: a re-test that names digest_from only in its evidence text rc=1 (expected 1)
ok GENUINE: a re-test whose digest the registry serves now rc=0 (expected 0)
ok FACT: a re-test whose digest the registry no longer serves rc=1 (expected 1)
ok FACT: a re-test with no new digest reaches the move gate too rc=1 (expected 1)
ok probe-measured: genuine komga note passes rc=0 (expected 0)
ok probe-measured: note moved into the TAGLINE rc=1 (expected 1)
ok probe-measured: date but no before/after rc=1 (expected 1)
ok probe-measured: before/after but no date rc=1 (expected 1)
ok probe-measured: 'read upstream' is not a measurement rc=1 (expected 1)
-- copy-i18n: the user guide (each FACT must be refused, naming the key)
ok GENUINE: a full Hungarian guide with its English overlay rc=0 (expected 0)
ok FACT: guide strings that are not in the freeze rc=1 (expected 1)
ok FACT: checked_version in the English block rc=1 (expected 1)
ok FACT: a section with an empty steps list rc=1 (expected 1)
ok FACT: an FAQ entry with a question and no answer rc=1 (expected 1)
ok FACT: an unquoted checked_version (YAML reads 4.10 as 4.1) rc=1 (expected 1)
ok FACT: more English sections than Hungarian rc=1 (expected 1)
ok FACT: an English section with a different step count rc=1 (expected 1)
ok FACT: an English guide with no Hungarian guide rc=1 (expected 1)
ok FACT: a tracker named in an English step rc=1 (expected 1)
ok FACT: a tracker named in a Hungarian answer (folded, any case) rc=1 (expected 1)
ok DECOY: a tracker named only in a YAML comment rc=0 (expected 0)
ok DECOY: a source-like word only in docs_url (configuration) rc=0 (expected 0)
-- guide-version (ADVISORY: WARN on the fact, silent on the label, exit 0 always)
ok FACT: the app's image moved off the checked tag (4.0.20 -> 4.0.21) rc=0 (advisory)
ok GENUINE: the guide is current rc=0 (advisory)
ok DECOY: the checked tag survives only on a SIDECAR rc=0 (advisory)
ok DECOY: checked_version only in a COMMENT rc=0 (advisory)
ok DECOY: checked_version only inside the English block rc=0 (advisory)
ok FACT: an empty checked_version is HIDDEN and named rc=0 (advisory)
ok image-pins: FACT: untagged rc=1 (expected 1)
ok image-pins: FACT: a registry PORT is not a tag rc=1 (expected 1)
ok image-pins: FACT: quoted :latest rc=1 (expected 1)
ok image-pins: FACT: :LATEST in capitals rc=1 (expected 1)
ok image-pins: FACT: a floating alias (:edge) rc=1 (expected 1)
ok image-pins: FACT: untagged with a comment saying pinned rc=1 (expected 1)
ok image-pins: FACT: a QUOTED key is the same field rc=1 (expected 1)
ok image-pins: FACT: an interpolated ref cannot be read rc=1 (expected 1)
ok image-pins: FACT: @sha256: with no digest behind it rc=1 (expected 1)
ok image-pins: INERT: a commented-out untagged image rc=0 (expected 0)
ok image-pins: INERT: an x- extension field (Compose ignores it) rc=0 (expected 0)
ok image-pins: INERT: an untagged image in README.md rc=0 (expected 0)
ok image-pins: GENUINE: a concrete tag rc=0 (expected 0)
ok image-pins: GENUINE: a registry port WITH a tag rc=0 (expected 0)
ok image-pins: GENUINE: a real digest rc=0 (expected 0)
ok image-pins: GENUINE: :latest pinned by its digest rc=0 (expected 0)
ok image-pins: GENUINE: the real catalog rc=0 (expected 0)
ok image-resolvable: GENUINE: every pin resolves rc=0 (expected 0)
ok image-resolvable: FACT: the registry says one pin is gone rc=1 (expected 1)
ok image-resolvable: LABEL: rc=0 carrying a throttle message rc=2 (expected 2)
ok image-resolvable: LABEL: rc=0 carrying any error text rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: a throttle on rc=1 is not an accusation rc=2 (expected 2)
ok image-resolvable: CRY-WOLF: an unrecognised failure is not an accusation rc=2 (expected 2)
ok image-resolvable: LABEL: a docker that resolves EVERYTHING, the canary too rc=3 (expected 3)
ok image-resolvable: LABEL: no docker at all rc=2 (expected 2)
ok image-resolvable: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: injected-prober suite OK
ok volume-persistence: LABEL: a runtime that answers nothing rc=3 (expected 3)
ok volume-persistence: LABEL: no docker at all rc=3 (expected 3)
ok volume-persistence: LABEL: nothing to judge rc=3 (expected 3)
ok volume-persistence: LABEL: not a scratch host rc=3 (expected 3)
-- onboarding: the facts (each MUST be refused)
ok FACT: a new template with NO record rc=1 (expected 1)
ok FACT: a record missing id 1.4 rc=1 (expected 1)
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
ok FACT: done with a path that does not exist rc=1 (expected 1)
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
ok FACT: n/a with a two-word reason rc=1 (expected 1)
ok FACT: an OPEN row rc=1 (expected 1)
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
-- onboarding: the genuine articles (each MUST pass)
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
-- family-gate: genuine and decoys
ok GENUINE: a family app, literal exceptions, min 0.287.0, golden 0.287.0 rc=0 (expected 0)
ok DECOY: family_gate only in a COMMENT -> not gated, nothing owed rc=0 (expected 0)
ok DECOY: no sibling -> rule 3 stated NOT CHECKED rc=0 (expected 0)
-- family-gate: the facts (each MUST be refused)
ok FACT: an exception that is a regex rc=1 (expected 1)
ok FACT: the exception '/' (the whole app) rc=1 (expected 1)
ok FACT: an exception with '..' rc=1 (expected 1)
ok FACT: an exception list with no gate rc=1 (expected 1)
ok FACT: min_controller only in a comment rc=1 (expected 1)
ok FACT: min_controller below the release rc=1 (expected 1)
ok FACT: newest baked golden below the release rc=1 (expected 1)
ok FACT: a golden DIRECTORY 0.287.0 with no bake log (a name is not a bake) rc=1 (expected 1)
ok FACT: glance (ruled family-gated, decision 221) without family_gate rc=1 (expected 1)
ok FACT: glance with family_gate only in a COMMENT (the label without the fact) rc=1 (expected 1)
ok FACT: grimmory (ruled, decisions 63/64) with family_gate: false rc=1 (expected 1)
ok GENUINE: glance gated, no exceptions rc=0 (expected 0)
-- mem-limit-sum: genuine and decoys
ok GENUINE: 384+256 = 640M, the field says 640M rc=0 (expected 0)
ok GENUINE: 1G is 1024M (1G + 256M = 1280M) rc=0 (expected 0)
ok DECOY: a steps/ file with an old figure is not judged rc=0 (expected 0)
-- mem-limit-sum: the facts (each MUST be refused)
ok FACT: the field under the sum; the right figure only in comments rc=1 (expected 1)
ok FACT: reservations: memory makes nothing a limit (service w/o limit) rc=1 (expected 1)
ok FACT: mem_limit only OUTSIDE resources: (top level) is no declaration rc=1 (expected 1)
ok FACT: an unreadable size is INCONCLUSIVE, never a pass rc=2 (expected 2)
-- db-credentials: genuine and decoys
ok GENUINE + DECOYS: comment, app-service ref, redis, literal - all not judged rc=0 (expected 0)
-- db-credentials: the facts (each MUST be refused)
ok FACT: list form, the variable has no deploy field rc=1 (expected 1)
ok FACT: list form, fed by a `type: password` field rc=1 (expected 1)
ok FACT: map form, the variable has no deploy field rc=1 (expected 1)
-- data-key: genuine and decoys
ok GENUINE: a flagged *_ENCRYPTION_KEY; a signing key labelled 'Encryption key' unflagged; bookstack registered rc=0 (expected 0)
-- data-key: the facts (each MUST be refused)
ok FACT: the flag only in a COMMENT rc=1 (expected 1)
ok FACT: the flag only inside the i18n: block rc=1 (expected 1)
ok FACT: a REGISTERED data key unflagged (bookstack APP_KEY) rc=1 (expected 1)
ok FACT: an unexplained flag (a signing key flagged) rc=1 (expected 1)
ok FACT: a stale REGISTRY entry (bookstack has no APP_KEY any more) rc=1 (expected 1)
ok FACT: a non-boolean flag is INCONCLUSIVE rc=2 (expected 2)
-- unclean-boot-images: genuine and decoys
ok GENUINE: the block lists the server's image rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (flow list) rc=0 (expected 0)
ok GENUINE: bumped AND the list updated (block list) rc=0 (expected 0)
ok DECOY: the new image only in a compose COMMENT rc=0 (expected 0)
ok DECOY: ANOTHER service bumped (machine-learning) rc=0 (expected 0)
ok DECOY: the block COMMENTED OUT, server bumped (no block, nothing owed) rc=0 (expected 0)
ok DECOY: a template with no block rc=0 (expected 0)
-- unclean-boot-images: the facts (each MUST be refused)
ok FACT: the server bumped, the list unchanged rc=1 (expected 1)
ok FACT: the same in a steps/ file (the ladder) rc=1 (expected 1)
ok FACT: the block names a service the compose does not run rc=1 (expected 1)
ok FACT: the block lists no images rc=1 (expected 1)
-- login-kind: genuine and decoys
ok GENUINE: login_kind first_visit rc=0 (expected 0)
ok GENUINE: login_kind email rc=0 (expected 0)
ok GENUINE: login_kind none rc=0 (expected 0)
ok GENUINE: quoted value rc=0 (expected 0)
ok DECOY: a bad value only in a COMMENT rc=0 (expected 0)
ok DECOY: a bad value only in the English block rc=0 (expected 0)
ok DECOY: the key under ANOTHER mapping rc=0 (expected 0)
-- login-kind: the facts (each MUST be refused)
ok FACT: an unknown value rc=1 (expected 1)
ok FACT: a wrong case rc=1 (expected 1)
ok FACT: the key twice rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
-- image-volumes: genuine
ok GENUINE: a named volume AT /romm/assets rc=0 (expected 0)
ok GENUINE: a bind AT /romm/assets, quoted rc=0 (expected 0)
ok GENUINE: an image not in the registry is not judged rc=0 (expected 0)
-- image-volumes: decoys and facts (each MUST be refused)
ok DECOY: the mount only in a COMMENT rc=1 (expected 1)
ok DECOY: the mount on ANOTHER service rc=1 (expected 1)
ok DECOY: the PARENT /romm mounted rc=1 (expected 1)
ok DECOY: a CHILD /romm/assets/x mounted rc=1 (expected 1)
ok DECOY: only a top-level volume, never mounted rc=1 (expected 1)
ok FACT: a steps/ file without it rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
-- redis-persistence: genuine and decoys (each passes)
ok GENUINE: --appendonly no rc=0 (expected 0)
ok GENUINE: no command (the image default is RDB) rc=0 (expected 0)
ok DECOY: --appendonly yes only in a COMMENT rc=0 (expected 0)
ok DECOY: AOF only in a steps/ file (a tested step, not judged) rc=0 (expected 0)
-- redis-persistence: the facts (each MUST be refused)
ok FACT: AOF in string form rc=1 (expected 1)
ok FACT: AOF in a flow list rc=1 (expected 1)
ok FACT: AOF in a block list rc=1 (expected 1)
ok FACT: AOF on a valkey image rc=1 (expected 1)
ok FACT: no templates/ at all (nothing to judge) rc=3 (expected 3)
catalog gate decoys OK — 252 case(s), every label judged on its fact (R-421)
rc=0
@@ -0,0 +1,22 @@
=== demo-hp
containers: 24 not healthy/running: none
2026/10/17 08:20:14 updater.go:855: [INFO] [selfupdate] Post-update startup: update successful (0.325.0-rc.2 → 0.325.0)
samba: not running (sharing off)
paperless-redis: ["redis-server","--appendonly","no"] aof_enabled:0
docmost-redis: ["redis-server","--appendonly","no"] aof_enabled:0
romm-redis: ["redis-server","--appendonly","no"] aof_enabled:0
=== felhom-pve
containers: 9 not healthy/running: none
2026/10/17 08:20:04 [INFO] [selfupdate] Post-update startup: update successful (0.324.0 → 0.325.0)
samba: not running (sharing off)
=== -J demo-hp root@192.168.0.154
containers: 17 not healthy/running: none
2026/10/17 08:20:16 [INFO] [selfupdate] Post-update startup: update successful (0.324.0 → 0.325.0)
samba: not running (sharing off)
paperless-redis: ["redis-server","--appendonly","yes"] aof_enabled:1
felhom.enkisfelhom.hu /login: 200
felhom.demo-felhom.eu /login: 200
felhom.enkicsifelhom.hu /login: 200
@@ -0,0 +1,4 @@
integrity: ok
('min_controller_version', '0.325.0')
('min_controller_version_declared_min_agent', '0.325.0=0.158.1')
per-box floors still set: []
@@ -0,0 +1,5 @@
global floor 2026-10-17T08:21:31Z: HTTP/1.1 303 See Other Location: /configuration?flash=floor_set
clear per-box tester-1: HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set
clear per-box demo-hp: HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set
clear per-box demo-felhom: HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set
hub /configuration page: 5 0.158.1 3 0.325.0
@@ -0,0 +1,40 @@
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
== tester-1 (enkicsifelhom.hu): 13 public name(s)
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
== demo-hp (enkisfelhom.hu): 14 public name(s)
OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
== demo-felhom (demo-felhom.eu): 8 public name(s)
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
== every public name redirects http -> https
@@ -0,0 +1,3 @@
per-box tester-1 2026-10-17T08:20:02Z: HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set
per-box demo-hp 2026-10-17T08:20:02Z: HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set
per-box demo-felhom 2026-10-17T08:20:02Z: HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set
@@ -0,0 +1,5 @@
#!/bin/bash
# boxver.sh — the controller image on the three test boxes, one line each (read-only).
for h in demo-hp felhom-pve "-J demo-hp root@192.168.0.154"; do
printf '%-34s %s\n' "$h" "$(ssh -o ConnectTimeout=15 -o BatchMode=yes $h "LC_ALL=C pct exec 9201 -- docker ps --filter name=^felhom-controller$ --format '{{.Image}} {{.Status}}'" 2>/dev/null | grep -v '^perl')"
done
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# damage.sh — R-1096 step 3, inside scratch guest 9202. Throwaway containers named r1096-*, removed at the end.
# Input: /root/r1096/aof-copy = docmost-redis's /data copied while it ran the OLD form (AOF), taken before the switch.
set -u
IMG=redis:7-alpine
W=/root/r1096/work; rm -rf $W; mkdir -p $W
say() { echo "== $*"; }
run_on() { # run_on <dir> <args...>: start redis on a COPY of <dir>, report whether it serves and its last log lines
local d=$1; shift
docker rm -f r1096-probe >/dev/null 2>&1
docker run -d --name r1096-probe -v $d:/data $IMG redis-server "$@" >/dev/null
sleep 4
echo " state: $(docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}}' r1096-probe)"
echo " dbsize: $(docker exec r1096-probe redis-cli DBSIZE 2>&1 | head -1) probe key 7: $(docker exec r1096-probe redis-cli GET felhom:probe:7 2>&1 | cut -c1-6)…"
docker logs r1096-probe 2>&1 | grep -iE 'Bad file|Unrecoverable|Short read|corrupt|loaded|fix|Ready|aborting|error|Done loading' | tail -4 | sed 's/^/ log: /'
docker rm -f r1096-probe >/dev/null 2>&1
}
say "A1 CONTROL: the old-form copy, undamaged, --appendonly yes"
cp -a /root/r1096/aof-copy $W/a1; chown -R 999:1000 $W/a1
run_on $W/a1 --appendonly yes
say "A2 the old form, damaged the 2026-09-24 way: 64 zero bytes written INTO the AOF, 400 bytes before its end"
cp -a /root/r1096/aof-copy $W/a2; chown -R 999:1000 $W/a2
F=$W/a2/appendonlydir/appendonly.aof.1.incr.aof; S=$(stat -c %s $F)
dd if=/dev/zero of=$F bs=1 seek=$((S-400)) count=64 conv=notrunc status=none
echo " file $S bytes, zeros at $((S-400))..$((S-337)); size after $(stat -c %s $F)"
run_on $W/a2 --appendonly yes
say "R1 the NEW form on a FULL disk (tmpfs 1 MiB): a save that cannot be written"
docker rm -f r1096-full >/dev/null 2>&1
docker run -d --name r1096-full --tmpfs /data:rw,size=1m,uid=999,gid=1000 $IMG sleep infinity >/dev/null
docker exec r1096-full sh -c 'mkdir -p /data/r && chown redis /data/r && su redis -s /bin/sh -c "redis-server --port 6390 --dir /data/r --appendonly no --daemonize yes --logfile /data/r.log" ' ; sleep 1
docker exec r1096-full sh -c 'for i in $(seq 1 50); do redis-cli -p 6390 SET felhom:probe:$i v$i >/dev/null; done; redis-cli -p 6390 SAVE; sha256sum /data/r/dump.rdb; ls -la /data/r'
echo " now 1.2 MB of new data, which cannot fit:"
docker exec r1096-full sh -c 'head -c 900000 /dev/urandom | base64 -w0 | redis-cli -p 6390 -x SET big >/dev/null; redis-cli -p 6390 BGSAVE; sleep 2; redis-cli -p 6390 INFO persistence | grep -E "rdb_last_bgsave_status|rdb_changes_since"; redis-cli -p 6390 SET after-fail 1; sha256sum /data/r/dump.rdb; ls -la /data/r; df -k /data | tail -1; tail -3 /data/r.log'
echo " stop WITHOUT saving (what a kill does), start again on the same folder:"
docker exec r1096-full sh -c 'redis-cli -p 6390 SHUTDOWN NOSAVE; sleep 1; su redis -s /bin/sh -c "redis-server --port 6390 --dir /data/r --appendonly no --daemonize yes --logfile /data/r2.log"; sleep 1; echo " dbsize after start: $(redis-cli -p 6390 DBSIZE) probe 7: $(redis-cli -p 6390 GET felhom:probe:7)"; grep -E "Done loading|Short read|Unrecoverable" /data/r2.log | tail -2'
docker rm -f r1096-full >/dev/null
say "R2 what a TRUNCATED dump.rdb does (half the file) — the shape the temp-file-and-rename write never leaves behind"
mkdir -p $W/r2; cp /var/lib/docker/volumes/docmost_docmost_redis_data/_data/dump.rdb $W/r2/full.rdb
S=$(stat -c %s $W/r2/full.rdb); head -c $((S/2)) $W/r2/full.rdb > $W/r2/dump.rdb; rm $W/r2/full.rdb; chown -R 999:1000 $W/r2
echo " dump.rdb cut from $S to $(stat -c %s $W/r2/dump.rdb) bytes"
run_on $W/r2 --appendonly no
rm -rf $W
say "cleanup: $(docker ps -a --filter name=r1096- --format '{{.Names}}' | wc -l) r1096-* containers left"
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""redis6a.py — R-1096 Part 6a on scratch guest 9202 (demo-hp), THROUGH THE PRODUCT.
Runs on DooPlex. Env: SC (0600 dir with .ctlpw), EV (evidence dir), TARGET=9202. One step per call:
install deploy a throwaway docmost (generated passwords, never printed)
seed 100 keys felhom:probe:<n> into docmost-redis (redis-cli inside the container); the expected
values go to SC/expected.txt, their sha256 to the log
check TAG read the 100 keys back and compare; INFO persistence; the redis container's Created + Cmd
restart the dashboard's restart (POST /api/stacks/docmost/restart: up -d --force-recreate)
sync POST /api/sync + rescan (the dashboard's „Sablonok frissítése"), then wait until docmost-redis
runs the catalog's new command (decision 4: the box applies a template correction itself)
backup „Mentés most" (POST /api/backup/run: db dumps + volume dumps with each stack STOPPED), polled
flush FLUSHALL in docmost-redis — the state a restore must undo
restore the backups page's „Visszaállítás" from docmost's own newest copy (POST /backup/restore)
remove remove docmost through the product (stop, remove with drive data + backups)
Nothing writes app data except redis-cli SET/FLUSHALL on the probe keys and the product's own buttons.
"""
import hashlib
import json
import os
import secrets
import sys
import time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w # noqa: E402
EV = os.environ["EV"]
LOG = open(os.path.join(EV, "6a-console.txt"), "a", buffering=1)
_say = w.say
APP, SUB, RC = "docmost", "docmost-r1096", "docmost-redis"
N = 100
def say(*a):
_say(*a)
LOG.write(time.strftime("%H:%M:%S ") + " ".join(str(x) for x in a) + "\n")
w.say = say
def rcli(cmd):
return w.guest(f"docker exec -i {RC} redis-cli {cmd} 2>&1").strip()
def redis_facts():
info = rcli("INFO persistence")
keep = [l for l in info.splitlines() if l.split(":")[0] in (
"aof_enabled", "rdb_last_bgsave_status", "rdb_changes_since_last_save", "rdb_last_save_time", "loading",
"aof_last_write_status", "rdb_saves")]
c = w.guest(f"docker inspect -f '{{{{.Created}}}} {{{{json .Config.Cmd}}}}' {RC} 2>&1; docker ps --filter name=^{RC}$ --format '{{{{.Status}}}}'")
files = w.guest(f"docker exec {RC} sh -c 'ls -la /data /data/appendonlydir 2>&1'")
return keep, " | ".join(c.split("\n")).strip(), files.strip()
def check(tag):
exp = [l.rstrip("\n").split(" ", 1) for l in open(os.path.join(w.SC, "expected.txt"))]
script = "".join(f"GET {k}\n" for k, _ in exp)
out = w.guest(f"docker exec -i {RC} redis-cli <<'R1096'\n{script}R1096\n").splitlines()
got = [o.strip().strip('"') for o in out][:N]
match = sum(1 for (k, v), g in zip(exp, got) if v == g)
keep, cont, files = redis_facts()
say(f"check {tag}: {match}/{N} probe keys returned with their value; dbsize={rcli('DBSIZE')}")
say(f" INFO persistence: {', '.join(keep)}")
say(f" container: {cont}")
say(f" /data: {files}")
return match
def main():
step, args = sys.argv[1], sys.argv[2:]
w.login()
if step == "install":
ok = w.deploy(APP, SUB)
say(f"install {APP}: {ok}")
st = w.stack(APP)
say(f" state={st.get('state')} pinned={(st.get('app_config') or {}).get('pinned_images')}")
elif step == "seed":
exp = [(f"felhom:probe:{n}", f"v{n}-{secrets.token_hex(8)}") for n in range(N)]
with open(os.path.join(w.SC, "expected.txt"), "w") as fh:
for k, v in exp:
fh.write(f"{k} {v}\n")
script = "".join(f"SET {k} {v}\n" for k, v in exp)
out = w.guest(f"docker exec -i {RC} redis-cli <<'R1096'\n{script}R1096\n")
sha = hashlib.sha256("".join(f"{k}={v}\n" for k, v in exp).encode()).hexdigest()
say(f"seed: {out.count('OK')}/{N} SET OK; expected set sha256={sha}; dbsize now {rcli('DBSIZE')}")
say(f" app's own keys (non-probe): {rcli('--scan --count 1000').count(chr(10)) + 1 - N} (approx, scan)")
check("after-seed")
elif step == "check":
check(args[0])
elif step == "sync":
before = redis_facts()[1]
say(f"sync: redis before: {before}")
t0 = time.time()
w.sync_rescan()
for i in range(120):
cont = redis_facts()[1]
if "appendonly\",\"no" in cont.replace(" ", "") and "Up" in cont:
say(f" +{time.time()-t0:.0f}s docmost-redis runs the new command: {cont}")
break
if i % 6 == 0:
say(f" +{time.time()-t0:.0f}s waiting: {cont}")
w.sync_rescan()
time.sleep(10)
else:
say(" the new command never reached docmost-redis within 20 min")
st = w.stack(APP)
say(f" stack: state={st.get('state')} hold={st.get('hold_reason')!r}")
elif step == "restart":
say("restart: compose files on the box: " + " | ".join(w.guest(
f"grep -H 'redis-server' /opt/docker/stacks/{APP}/docker-compose.yml /opt/docker/stacks/{APP}/applied-compose.yml").split(chr(10))))
say(f" redis before: {redis_facts()[1]}")
code, d = w.ctl("POST", f"/api/stacks/{APP}/restart")
say(f" POST /api/stacks/{APP}/restart -> {code} {str(d)[:160]}")
for _ in range(60):
time.sleep(5)
st = w.stack(APP)
if st.get("state") == "running" and "healthy" in redis_facts()[1]:
break
say(f" stack: state={w.stack(APP).get('state')}; redis after: {redis_facts()[1]}")
say(" redis log: " + " | ".join(w.guest(f"docker logs {RC} 2>&1 | grep -iE 'DB loaded|Loading|RDB|AOF|Ready' | tail -6").split(chr(10))))
check("after-restart")
elif step == "backup":
code, d = w.ctl("POST", "/api/backup/run")
say(f"backup: POST /api/backup/run -> {code} {str(d)[:160]}")
t0 = time.time()
while time.time() - t0 < 1800:
done = w.guest("docker logs --since 30m felhom-controller 2>&1 | grep -E 'App-data backup completed|backup.*(FAILED|failed)' | tail -2")
if "completed" in done:
say(f" +{time.time()-t0:.0f}s {done.strip()}")
break
time.sleep(10)
else:
say(" no completion line in 30 min")
say(" volume dumps: " + w.guest("docker logs --since 30m felhom-controller 2>&1 | grep -iE 'docmost' | grep -iE 'volume|dump|stopp|start' | tail -8"))
say(" units: " + w.guest("find / -xdev -path '*backups/primary/docmost/volume-dumps/*redis*' -newermt '-40 min' -printf '%TT %s %p\\n' 2>/dev/null | head"))
elif step == "flush":
say(f"flush: FLUSHALL -> {rcli('FLUSHALL')}; dbsize {rcli('DBSIZE')}")
elif step == "restore":
r = w.restore(APP)
say("restore: " + json.dumps({k: r.get(k) for k in ("ok", "snapshot_id", "http", "location", "seconds")}, ensure_ascii=False))
time.sleep(20)
check("after-restore")
elif step == "remove":
say(f"remove: {w.remove(APP)}")
else:
sys.exit("unknown step " + step)
if __name__ == "__main__":
main()
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""redis6c.py — R-1096 6c on a test box after the catalog push: sync, then per app the dashboard's restart, and the
Redis facts before/after (DBSIZE, INFO persistence, the container's Created + Cmd). Env SC, EV, TARGET; args: apps.
`--no-restart` only reads (a box where CC presses nothing)."""
import os, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w # noqa: E402
EV = os.environ["EV"]
LOG = open(os.path.join(EV, f"6c-{w.TARGET}.txt"), "a", buffering=1)
_say = w.say
def say(*a):
_say(*a); LOG.write(time.strftime("%H:%M:%S ") + " ".join(str(x) for x in a) + "\n")
w.say = say
def facts(app):
rc = f"{'paperless' if app == 'paperless-ngx' else app}-redis"
out = w.guest(f"""echo "compose: $(grep -h 'redis-server' /opt/docker/stacks/{app}/docker-compose.yml /opt/docker/stacks/{app}/applied-compose.yml | tr -s ' ' | tr '\\n' '|')"
echo "container: $(docker inspect -f '{{{{.Created}}}} {{{{json .Config.Cmd}}}}' {rc}) $(docker ps --filter name=^{rc}$ --format '{{{{.Status}}}}')"
echo "dbsize: $(docker exec {rc} redis-cli DBSIZE) $(docker exec {rc} redis-cli INFO persistence | grep -E 'aof_enabled|rdb_last_bgsave_status' | tr -d '\\r' | tr '\\n' ' ')"
echo "/data: $(docker exec {rc} ls /data | tr '\\n' ' ')" """)
return out.strip()
def main():
apps = [a for a in sys.argv[1:] if not a.startswith("--")]
w.login()
code, d = w.ctl("POST", "/api/sync")
say(f"POST /api/sync -> {code} {str(d)[:200]}")
time.sleep(5)
for app in apps:
say(f"== {app} before:\n{facts(app)}")
if "--no-restart" in sys.argv:
continue
c, d = w.ctl("POST", f"/api/stacks/{app}/restart")
say(f" POST /api/stacks/{app}/restart -> {c} {str(d)[:120]}")
for _ in range(60):
time.sleep(5)
if w.stack(app).get("state") == "running":
break
time.sleep(15)
st = w.stack(app)
say(f"== {app} after (state={st.get('state')} hold={st.get('hold_reason')!r}):\n{facts(app)}")
main()
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""repoint.py mirror|drill|restore|status — point a box (here scratch 9202, burndown 3) at the drill catalog WITHOUT a credential (R-992).
WHY A LOCAL MIRROR. `09` §6.5's route needs a catalog credential on the box: the drill repo is private (R-992). This
session could not mint a READ-ONLY one — the Gitea credential on DooPlex is itself a token, and Gitea answers 403 to a
token asking to list or create tokens (measured 2026-10-14) — and the night of 2026-10-06 put the admin WRITE token
into the box's controller.yaml, which this task forbids. The cache-staging workaround of 2026-10-12 does not hold for a
test that lasts days: every sync (and every controller start) re-applies the live catalog over the staged templates
and over the installed apps' compose files (Syncer.copyTemplates, no deployed check).
So the drill catalog is MIRRORED onto the box: a bare git repository inside the controller's own data volume, and
`git.repo_url: file:///opt/docker/felhom-controller/drill-catalog.git`. The controller's sync runs exactly as for a
remote (clone/fetch/reset, the R-615 re-clone on a changed URL); no secret is on the box. DooPlex updates the mirror
from the drill repo with a git bundle (`mirror`). The drill repo on Gitea stays the record.
SC=~/.felhom-tester1 TARGET=tester-1 python3 repoint.py mirror # (re)load the mirror from the drill repo main
... repoint.py drill # save controller.yaml (once), set repo_url to the mirror, restart, sync, rescan
... repoint.py restore # put the saved controller.yaml back (byte-identical), restart, sync, rescan
... repoint.py status
"""
import os, subprocess, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w
DRILL = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill"
VOL = "/var/lib/docker/volumes/felhom-controller-data/_data" # guest path of the controller's data volume
CVOL = "/opt/docker/felhom-controller" # the same, inside the controller container
SAVE = f"{VOL}/controller.yaml.pre-redis1017"
MIRROR_URL = f"file://{CVOL}/drill-catalog.git"
def status():
print(w.guest(f"""grep -nE 'repo_url|sync_interval' {VOL}/controller.yaml; ls -l {SAVE} 2>&1 | tail -1
docker exec felhom-controller sh -c 'git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main 2>&1;
git -C {CVOL}/data/catalog-cache log -1 --format="cache HEAD %h %s" 2>&1; git -C {CVOL}/data/catalog-cache remote get-url origin'"""))
def mirror():
b = "/tmp/drill-redis1017.bundle"
subprocess.run(["git", "-C", DRILL, "bundle", "create", b, "main"], check=True, capture_output=True)
head = subprocess.run(["git", "-C", DRILL, "rev-parse", "--short", "main"], capture_output=True, text=True).stdout.strip()
ssh = w.ssh_args()
# bundle -> PVE host -> guest -> controller volume; the bare repo is made and fetched BY the controller's own user
subprocess.run(["scp", "-q", "-o", "StrictHostKeyChecking=accept-new"] + (["-J", w._T["jump"]] if w._T["jump"] else [])
+ [b, f"{w._T['ssh']}:{b}"], check=True)
subprocess.run(ssh + [f"pct push {w.GUEST} {b} {VOL}/drill-redis1017.bundle && rm -f {b}"], check=True)
print(w.guest(f"""set -e
docker exec felhom-controller sh -c 'test -d {CVOL}/drill-catalog.git || git init -q --bare -b main {CVOL}/drill-catalog.git
git -C {CVOL}/drill-catalog.git fetch -q {CVOL}/drill-redis1017.bundle +main:main && rm -f {CVOL}/drill-redis1017.bundle
git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main'"""))
print("drill repo main:", head)
os.remove(b)
def restart_sync():
w.guest("docker restart felhom-controller >/dev/null")
for _ in range(60):
time.sleep(3)
try:
w.login()
break
except SystemExit:
continue
w.sync_rescan()
time.sleep(3)
print(w.guest("docker logs --since 3m felhom-controller 2>&1 | grep -E '\\[sync\\]' | grep -v DEBUG | tail -8"))
def drill():
print(w.guest(f"""set -e
test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE}
sed -i -E 's#^(\\s+repo_url: ).*$#\\1{MIRROR_URL}#' {VOL}/controller.yaml
grep -n 'repo_url' {VOL}/controller.yaml; diff {SAVE} {VOL}/controller.yaml || true"""))
restart_sync()
status()
def restore():
print(w.guest(f"""set -e
cp -p {SAVE} {VOL}/controller.yaml
cmp {SAVE} {VOL}/controller.yaml && echo RESTORED-IDENTICAL; grep -n repo_url {VOL}/controller.yaml"""))
restart_sync()
status()
{"mirror": mirror, "drill": drill, "restore": restore, "status": status}[sys.argv[1]]()
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""samba1109.py — R-1109 live on demo-hp 9201 through the dashboard's own form posts (the „Megosztás" page).
Env: SC (0600 dir: .ctlpw, and .smbpw written here — never printed), EV, TARGET=9201. Steps:
setup sharing on (server name FELHOMHP), a household SMB password, share r1109a in a NEW folder on hdd_1
read NAME smbclient //192.168.0.155/NAME from SCRATCH GUEST 9202 (a throwaway --rm felhom-samba client
container there — nothing runs on DooPlex): write, list, read back, delete; plus a wrong password
rename A B the household's rename: delete share A, add share B on the SAME folder („existing")
add NAME add a second share NAME on the same folder as r1109b
delete NAME delete a share
facts felhom-samba's Created + label, the smb.conf share sections, the compose's binds
teardown delete the test shares, sharing off, remove the test folder
"""
import os, secrets, subprocess, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/night-restore-2026-10-17/tools")
import box_walk as w # noqa: E402
EV = os.environ["EV"]
LOG = open(os.path.join(EV, "r1109-samba-live.txt"), "a", buffering=1)
_say = w.say
def say(*a):
_say(*a); LOG.write(time.strftime("%H:%M:%S ") + " ".join(str(x) for x in a) + "\n")
w.say = say
import drill # noqa: E402 (form(): a dashboard FORM post with the session's CSRF)
import re # noqa: E402
# drill.form logs the fields it posts — a password field is redacted BEFORE the line is written anywhere (2026-10-17:
# the first run logged the throwaway test password; scrubbed, and the password replaced at teardown).
drill.say = lambda *a: say(*[re.sub(r"('smb_password(?:_confirm)?': )'[^']*'", r"\1'<redacted>'", str(x)) for x in a])
ROOT = "/mnt/felhom-drives/hdd_1"
DIR = ROOT + "/shares/r1109a"
IMG = "gitea.dooplex.hu/admin/felhom-samba:1.2.0"
def facts():
out = w.guest("""docker inspect felhom-samba -f 'samba: created {{.Created}} label={{index .Config.Labels "felhom.smb-conf-sha256"}} status={{.State.Status}}' 2>&1
d=/opt/docker/stacks/felhom-samba; [ -d $d ] || d=$(dirname $(ls /opt/docker/stacks/*/smb.conf | head -1))
echo "smb.conf shares: $(grep -E '^\\[' $d/smb.conf | tr '\\n' ' ')"
echo "smb.conf hash: $(sha256sum $d/smb.conf | cut -c1-16)… compose binds: $(grep -E '^ - /mnt' $d/docker-compose.yml | tr -s ' ' | tr '\\n' ' ')"
echo "inside the container: $(docker exec felhom-samba grep -E '^\\[' /etc/samba/smb.conf | tr '\\n' ' ')" """)
say(out.strip())
def read(name):
auth = f"/root/r1109-auth-{os.getpid()}"
pw = open(f"{w.SC}/.smbpw").read().strip()
script = (f"umask 077; printf 'username=felhom\\npassword=%s\\n' \"$(cat)\" > {auth}; "
f"printf 'username=felhom\\npassword=not-the-password\\n' > {auth}.bad; docker pull -q {IMG} >/dev/null; "
f"docker run --rm --network host -v {auth}:/auth:ro --entrypoint sh {IMG} -c "
f"\"echo r1109-{name} > /tmp/p.txt && smbclient //192.168.0.155/{name} -A /auth -m SMB3 -c 'put /tmp/p.txt probe-{name}.txt; ls probe-*; get probe-{name}.txt /tmp/b.txt; rm probe-{name}.txt' 2>&1 | grep -vE '^\\s*$|blocks of size'; echo \\\"read back: \\$(cat /tmp/b.txt 2>/dev/null || echo NOTHING)\\\"\"; "
f"echo 'wrong password:' $(docker run --rm --network host -v {auth}.bad:/auth:ro --entrypoint smbclient {IMG} //192.168.0.155/{name} -A /auth -m SMB3 -c ls 2>&1 | tail -1); "
f"rm -f {auth} {auth}.bad")
# The password travels on STDIN only: the first line is read into a shell variable on the Proxmox host and piped
# to the guest script by printf (a builtin — never an argv, never a file); the script follows on the same stdin.
r = subprocess.run(w.ssh_args() + ["umask 077; IFS= read -r PW; cat > /tmp/r1109.sh; pct push 9202 /tmp/r1109.sh /root/r1109.sh; "
"rm -f /tmp/r1109.sh; printf '%s' \"$PW\" | pct exec 9202 -- bash /root/r1109.sh; "
"pct exec 9202 -- rm -f /root/r1109.sh"],
input=pw + "\n" + script, capture_output=True, text=True, timeout=300)
say(f"read //{name} from 9202:\n" + "\n".join(l for l in (r.stdout + r.stderr).splitlines() if not l.startswith("perl:") and "locale" not in l and "LC_" not in l and "LANG" not in l and "are supported" not in l))
def main():
step, args = sys.argv[1], sys.argv[2:]
w.login()
if step == "setup":
drill.form("/sharing/enable", {"enabled": "on", "server_name": "FELHOMHP"})
pw = secrets.token_urlsafe(18)
fd = os.open(f"{w.SC}/.smbpw", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600); os.write(fd, pw.encode()); os.close(fd)
drill.form("/sharing/password", {"smb_password": pw, "smb_password_confirm": pw})
for _ in range(60):
time.sleep(5)
c, d = w.ctl("GET", "/sharing/status")
if "running" in str(d).lower() and "prepar" not in str(d).lower():
break
say("status: " + str(d)[:300])
drill.form("/sharing/shares", {"name": "r1109a", "mode": "new", "storage_root": ROOT})
time.sleep(8); facts()
elif step == "read":
read(args[0])
elif step == "rename":
a, b = args
drill.form("/sharing/shares/delete", {"name": a}); time.sleep(6)
drill.form("/sharing/shares", {"name": b, "mode": "existing", "path": DIR}); time.sleep(10); facts()
elif step == "add":
drill.form("/sharing/shares", {"name": args[0], "mode": "existing", "path": DIR}); time.sleep(10); facts()
say("controller log: " + w.guest("docker logs --since 2m felhom-controller 2>&1 | grep -iE 'samba|sharing' | grep -v DEBUG | tail -6"))
elif step == "delete":
drill.form("/sharing/shares/delete", {"name": args[0]}); time.sleep(10); facts()
elif step == "facts":
facts()
elif step == "teardown":
dead = secrets.token_urlsafe(18) # the household password replaced by an unlogged one before sharing goes off
drill.form("/sharing/password", {"smb_password": dead, "smb_password_confirm": dead})
time.sleep(5)
for n in args:
drill.form("/sharing/shares/delete", {"name": n})
drill.form("/sharing/enable", {"enabled": "off", "server_name": "FELHOMHP"})
time.sleep(10)
say("teardown: " + w.guest(f"rm -rf {DIR}; rmdir {ROOT}/shares 2>/dev/null; docker ps -a --filter name=felhom-samba --format '{{{{.Names}}}} {{{{.Status}}}}'; ls {ROOT}/shares 2>&1 | head -2"))
os.remove(f"{w.SC}/.smbpw")
else:
sys.exit("unknown step")
main()
@@ -27,3 +27,23 @@ unchecked); the AOF → RDB switch keeping existing data is likely (Redis 7 writ
unproven. Also: R-1096's own history is wrong — `audits/r672-2026-09-24/B7-redis-aof-fix.txt` shows RomM's AOF WAS
repaired on 2026-09-24 (cut by 6 631 B, fix_rc=0, romm-redis then running); the 2026-10-16 crash loop has no proven
cause (its Redis log was never read).
## Update 2026-10-17 (day, burndown 3) — DONE for AOF → RDB; the volume removal deferred
- **Proved first on scratch 9202** (`audits/burndown-3-2026-10-17/P6/`): docmost, 100 probe keys; the RDB line delivered
through a catalog (the drill, mirrored into the box) and the dashboard's restart → 100/100 back, `aof_enabled:0`,
`rdb_last_bgsave_status:ok` (Redis 7 wrote `dump.rdb` at the AOF instance's shutdown and loaded it); the nightly
volume dump + FLUSHALL + the backups page's restore → 100/100 back. Damage: 64 zero bytes in the AOF → „Bad file format
… use ./redis-check-aof --fix", exit 1; RDB on a full 1 MiB tmpfs → BGSAVE `err`, old `dump.rdb` byte-identical,
restart loads it (50 keys); a HALF-CUT `dump.rdb` also refuses to start („RDB ERROR DETECTED") — the write path never
leaves one (temp file + rename).
- **Changed:** catalog `5d9450b` — the five templates `--appendonly no`; `steps/` files unchanged (tested definitions,
`09` §6.4 part 5; a frozen app is frozen whole, `09` §3 decision 4 / `sync/sync.go` renderSource). Gate
`redis-persistence`. A box takes it at the app's next start: demo-hp 9201 restarted through the dashboard 2026-10-17
(romm, docmost, paperless — each `aof_enabled:0`, healthy).
- **Deferred (R-1117):** removing the Immich/Nextcloud Redis volume (tmpfs) — it leaves the old volume on installed boxes
and the nightly dump / old-backup restore behaviour for a removed volume is unchecked; the RDB switch already removes
the danger.
- The old `appendonlydir/` stays in each volume (ignored with `--appendonly no`; ~0.3 MB on docmost). A box whose AOF is
ALREADY damaged should now start from its last snapshot, since `--appendonly no` never reads the AOF (reasoned, NOT
measured).
+13
View File
@@ -26,6 +26,19 @@
---
## 2026-10-17 — burndown 3: limiters, family fingerprints, samba label, path guard, gates in a worktree, Redis RDB (controller v0.325.0, catalog `33a4b3b` + `5d9450b`)
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1096** | **After a disk-full, an app's damaged Redis file keeps the app down for good, and nothing says what to do.** (P3) | CLOSED 2026-10-17 — catalog `5d9450b`: romm, docmost, paperless-ngx, immich, nextcloud Redis `--appendonly no`. Proved first on scratch 9202 (100/100 keys through the switch and through dump + restore; damaged AOF refuses to start; RDB on a full disk keeps the old file). `steps/` unchanged (`09` §6.4 part 5). Gate `redis-persistence`. demo-hp 9201: three apps restarted, each `aof_enabled:0`. Tester 1 stays on the drill catalog (R-1022). `07` [DESIGN] paragraph. Volume removal deferred → R-1117. | `audits/burndown-3-2026-10-17/` |
| **R-1109** | **A change that alters only `smb.conf` (not the compose file — e.g. a share renamed at the same path) is never applied to a running samba container.** (P4) | CLOSED 2026-10-17 — **controller v0.325.0**: the samba compose carries `felhom.smb-conf-sha256`; an smb.conf-only change recreates the container, an unchanged one renders a byte-identical compose. Tests red-proved. Live on 9201: share added, renamed on the same folder, a second share on the same folder — each read/written by `smbclient` from 9202, the old name refused, the label = the smb.conf hash at every step. Measured too: through the dashboard a rename was already applied (delete and add each change the compose); the label covers a controller update that changes smb.conf alone. Every box with sharing on recreates samba once. | `audits/burndown-3-2026-10-17/` |
| **R-1113** | **`/__family/start` with a household dashboard session creates a NEW 30-day household family session on every pass, so `family.json` keeps growing.** (P4) | CLOSED 2026-10-17 — **controller v0.325.0**: `Store.HouseholdSession` reuses the household's live family session (a new one only under half of its 30 days). `TestR1113_HouseholdPassesLeaveOneSession` (6 passes → 1), red-proved; live on 9201: 3 browser passes, household sessions stayed 2. | `audits/burndown-3-2026-10-17/` |
| **R-1112** | **Family sessions are stored in `family.json` as the raw session value, not as a fingerprint the way dashboard sessions are (R-35)** (P3) | CLOSED 2026-10-17 — **controller v0.325.0**: `family.json` holds `fp` (hex sha256 of the cookie); an old file is converted and rewritten — at controller START (rc.1 opened the store lazily; found live on demo-hp 9201, fixed in rc.2: „2 session(s) … rewritten as fingerprints”). Tests `TestR1112_*` (4) + `TestFamily_OldFileThatCannotBeRewrittenIsAnError`, each red-proved; headless Chrome ×3 through the family gate on 9201. | `audits/burndown-3-2026-10-17/` |
| **R-1111** | **The share-password limiter (`/s/<token>`, `shareRateLimited`) and the escrow re-auth limiter (`escrowRateLimited`) still count a try only AFTER the slow password check, so parallel tries get past them** (P3) | CLOSED 2026-10-17 — **controller v0.325.0**: one limiter body `reserveAttempt` for the dashboard sign-in, the share password (`shareReserve`) and the escrow re-auth (the login counter): the try is reserved before the compare. `TestR1111_ParallelSharePasswordsHitTheLimit`, `TestR1111_ParallelEscrowReauthsHitTheLimit` (20 parallel tries → at most 5 compares), red-proved. REUSE.md row updated; `04` §6.3 now records the four password doors. | `audits/burndown-3-2026-10-17/` |
| **R-1110** | **The R-1017 guard covers `UserdataDir` only; two sibling path helpers are still called with drive-named arguments whose comments claim a namespace root:** (P4) | CLOSED 2026-10-17 — **controller v0.325.0**: `TestNoBareDrivePathToPathHelpers` reads all 42 calls of `RecoveryUnitPath`/`AppDataDir`/`PrimaryBackupPath` in both packages. None passed a bare drive path; two only by a comment: `GetStackBackupData`'s parameter renamed `nsRoot` (its caller passes `AppNamespaceRoot`), `appDetailsForPath` now goes through `NamespaceRootFor`. Loop variables renamed in three files. Red-proved three ways. | `audits/burndown-3-2026-10-17/` |
| **R-1114** | **`controller_gates.py` cannot pass inside a worktree outside `git/` (e.g. `/mnt/5_hdd/felhom.eu/wt/<name>`): the reuse-refs, instructions and observations gates look for felhom.eu only at `../felhom.eu`, report MISSING, and the runner fails.** (P4) | CLOSED 2026-10-17 — **controller v0.325.0** + felhom.eu `0314b8e7`: `controller/scripts/shared_scripts.py` (env, else the main clone's sibling via `git worktree list`, else `../felhom.eu`) for `controller_gates.py`, `retrieval_promise_gate.py`, `golden_notice.py`; `reuse_refs_check.py` reads `FELHOM_WORKSPACE` and skips the worktree's own main clone. Tests red-proved; the runner passed in a worktree under `/mnt/5_hdd/felhom.eu/wt/` and in the clone. | `audits/burndown-3-2026-10-17/` |
| **R-1115** | **The catalog decoy suite fails one case: „FACT: a credential token rewritten in translation" — `check-copy-i18n.py` answers rc 0 where the decoy expects 1, so the gate may no longer convict a reworded credential token.** (P3) | CLOSED 2026-10-17 — the DECOY moved, not the gate: `326f846` (R-1102) removed gokapi's Hungarian login line, and the case appended an English login to a template with nothing to compare. The case now rewrites wger's existing English line in place (a removed premise stops the suite), plus a new case (calibre-web `admin123` → `admin1234`); both refused, rc 1, the old case rc 0 on today's tree. `gate-decoys` is a row of `catalog_gates.py`'s full run (196 s measured, PyYAML — not `--fast`); `test_catalog_gates.py`, red since the night before (count 18), fixed. Copy gate over the whole catalog: OK, no template convicted. Catalog `33a4b3b`. | `audits/burndown-3-2026-10-17/` |
## 2026-10-17 — night: restore after a reinstall, restic 0.19.1, hardening (controller v0.324.0, catalog `af75dc0`)
| Row | What | Closed | Evidence |
+7 -13
View File
@@ -124,7 +124,7 @@ stopping line that lies.
| **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC |
| **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator |
## Apps & catalog — 20 rows (P2 1, P3 11, P4 8)
## Apps & catalog — 20 rows (P2 1, P3 10, P4 9)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -145,9 +145,9 @@ stopping line that lies.
| **R-995** | Apps & catalog | P4 | **Removing an app through the product can leave unnamed Docker volumes and an empty import folder behind.** MEASURED 2026-10-13 on demo-felhom after 61 removals (`documentation/audits/sizing-2026-10-13/METHOD.md` „Teardown”, `evidence/leftover-volumes-after-bench.txt`): 8 unnamed volumes (4–72 KB; immich 1, romm 2, privatebin 3, onlyoffice 2 — volumes the images declare themselves) and `userdata/import/{paperless,calibre,grimmory}` (empty, created by the deploy). The removal answered 200 and its own leftover check was clean, because it looks only at labelled volumes. No data risk; disk and tidiness only. Removed by name on the bench. | **READY** — filed 2026-10-13 | — | Make the removal account for the containers' unnamed volumes (and an empty import folder it created), with a test that deploys an image that declares a VOLUME | CC |
| **R-1067** | Apps & catalog | P4 | **The catalog still carries `pi_compatible:` on every template, and nothing reads it any more.** controller v0.315.0 removed the „Pi kompatibilis" badge and the „csak x86" tag from every page (Raspberry Pi is no longer supported, so one was noise and the other was a warning on almost every app). The `ResourceHints.PiCompatible` struct field and the catalog key were deliberately LEFT: a box still on 0.314.0 reads the key, and removing it would make that box show „csak x86" on every app. **Gated on the floor:** once the fleet floor is at or above 0.315.0, drop `pi_compatible:` from the templates and the field from `stacks.ResourceHints` in the same pair of commits. Harmless until then — an unread key costs nothing. | **BLOCKED** — filed 2026-10-15. Checked 2026-10-16 (apps burndown): the floor is 0.321.0 and demo-hp, Tester 1 and the N100 run 0.321.0, but **Tester-2 still reports 0.292.0** (its newest report 2026-10-04; hub DB with WAL) — so nothing removed. | every box on ≥ 0.315.0 — today Tester-2 | Drop the catalog key and the struct field; grep the hub and the website for Pi wording at the same time (none found 2026-10-15) | CC |
| **R-1043** | Apps & catalog | P4 | **Sonarr, Radarr and Prowlarr build their login redirect as an absolute `http://` URL.** They trust forwarded headers only from their „Trusted networks” (the household set `192.168.0.0/24`; traefik is `172.18.x`), so they ignore `X-Forwarded-Proto: https` and answer `Location: http://<name>/login` to an https visitor. Since controller v0.312.0 that hop is sent back to https by traefik (a GET, no password), so it is cosmetic; a template could seed traefik's network as trusted. (`audits/https-only-2026-10-14/``1A-3-4.txt`) | **OPEN** — filed 2026-10-14 | — | Seed the docker network as a trusted proxy in the *arr templates (with the R-1030 seed), measure the redirect | CC |
| **R-1096** | Apps & catalog | P3 | **After a disk-full, an app's damaged Redis file keeps the app down for good, and nothing says what to do.** FOUND 2026-10-16 on demo-hp 9201: the standing RomM (installed 2026-08-21) was stopped; the dashboard's Start re-created it, `romm-redis` crash-looped (unhealthy), the box stopped it again (`crash_loop`, `09` §3 decision 28) and mailed `app_start_failed`. Its Redis volume dates from the 2026-09-24 thin-pool fill (R-672): Docmost's damaged append-only file was repaired by hand that day with `redis-check-aof --fix` (`audits/r672-2026-09-24/B7-redis-aof-fix.txt`); RomM's was not (the copy `romm_romm_redis_data.pre-aof-fix-20260924T135937Z` is still on the box). The Redis log was not read: the box removed the containers first. That RomM was then removed by operator ruling (`audits/romm-ghost-2026-10-16/A1/`). | **READY** — 2026-10-17 night: researched, NOT changed (`audits/night-restore-2026-10-17/P6/redis-persistence.md`). Seven Redis services: romm, docmost, paperless-ngx, immich, nextcloud run append-only (AOF) — a file damaged in the middle stops Redis until `redis-check-aof --fix`; outline and dawarich run RDB snapshots, which a full disk cannot damage. Proposed: AOF → RDB for romm/docmost/paperless (queues + sessions), OFF (tmpfs) for immich/nextcloud (upstream runs them without a volume). Not edited unattended: the same lines sit in 7 ladder step files, a removed volume orphans on installed boxes, and keeping data across AOF → RDB is unproven. **Correction:** RomM's AOF WAS repaired on 2026-09-24 (`audits/r672-2026-09-24/B7-redis-aof-fix.txt`); the 2026-10-16 crash loop has no proven cause (its Redis log was never read). | — | Prove AOF → RDB keeps data on 9202 (and reproduce a damaged AOF there); decide the step-file rule; then the template change | CC |
| **R-1100** | Apps & catalog | P3 | **One sign-in for the dashboard and FileBrowser (proxy auth) — spike first.** Since controller 0.320.0 every app with a folder has „Fájlok ↗", and the box can show FileBrowser's password — but FileBrowser still asks for its OWN login the first time, so the household signs in twice. FileBrowser Quantum has a proxy-auth method (`auth.methods.proxyAuth`, header-trusted) that could take the dashboard session through traefik; nobody has measured it. Design: `architecture/02-controller-module-map.md` „Logins and folders". | OPEN — filed 2026-10-16 (task logins-folders). | — | Spike on demo-hp: proxy auth at the pinned tag (source + measured), a forwardAuth in traefik that answers only for a dashboard session, what a stranger reaching `files.<domain>` with a forged header gets, and what happens to FileBrowser's shares. | CC |
| **R-1099** | Apps & catalog | P4 | **The N100 may hold RomM games in the old upload place, now shown under a fake „roms” platform.** Until 2026-10-16 RomM's own upload wrote to the drive's `roms/roms/<platform>/`; after the mapping fix (catalog `fb18471`, R-1089) those files are at RomM's `library/roms/roms/…`. Measured on 9201: such a game showed as missing in its platform and again under a platform „roms” (`audits/romm-ghost-2026-10-16/B/b-after.txt`). Files put in with the file manager are not affected — they are now found. The N100 runs RomM (hub `/apps/romm`); CC does not log in to the lived-in box. | OPEN — filed 2026-10-16 | Operator: look at `roms/roms/` on the N100's drive in the file manager | If it holds platform folders: move them up one level into `roms/`, then run „Scan” in RomM; if it is empty, close | Operator |
| **R-1117** | Apps & catalog | P4 | **Immich's and Nextcloud's Redis keep a volume they do not need** (upstream runs both without one; queues and caches only). Deferred from R-1096 on purpose: removing a volume from the compose leaves the old volume on installed boxes, and the nightly dump / an old backup's restore for a volume the compose no longer declares is unchecked. The RDB switch (catalog `5d9450b`) already removed the full-disk danger. | DEFERRED — filed 2026-10-17 | — | Measure the dump and an old-backup restore without the volume on 9202; then the template change + an orphan-volume note | CC |
## App updates — 4 rows (P3 4)
@@ -193,7 +193,7 @@ stopping line that lies.
| **R-32** | Backup & restore | P4 | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** The rehearsal's S7 said in advance that an orphan card would BE a finding — and one appeared (16:58:14). Cause: RESET's `"hetzner":"ok"` leg destroys the sub-account, but **a Hetzner sub-account is an access-control object, not a data object** — its directory survives, so re-enabling offsite recreated an account over the previous lifecycle's ciphertext, encrypted under a key that same RESET had destroyed. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: design written** (`audits/night-burndown-2026-10-06/design-R-32.md`) — pick A: purge the repo + set-aside copies through the sub-account's own password login BEFORE deleting it (no main-account credential); the „repo data destroyed" comment at `hub/internal/offsite/offsite.go:276-280` is false for the shared tier; part (3) waits on a read-only measurement (`du` on port 23). Operator: agree to route A. **2026-10-07 07:58: `09` §3 decision 167 — option A yes, and delete the old RESET leftovers once.** **2026-10-07 (Part D step 2, read only):** the hub shows bx11 holding 3.3 GB of data while the four live customers account for ~0.6 GB → **~2.7 GB belongs to no live customer**; no live customer shows a set-aside folder, so the leftovers are in homes whose sub-account is gone, reachable only by the pool box's MAIN account. **The one-time clean-up is STOPPED:** no main-account login for bx11 is held on DooPlex (the API token in the credentials file sees no box). Needs: the main-account login, or the operator lists and deletes in the Hetzner console (`audits/day-2026-10-07/D/D2-listing-readonly.txt`). **2026-10-07: option A DELIVERED (hub 0.142.0):** RESET purges the repo and every `.orphaned-*` through the sub-account's own login before deleting it, and keeps the sub-account if the purge fails (red-proved, `audits/day-2026-10-07/D/`). Not yet seen live (no scratch RESET today). **The one-time clean-up of the ~2.7 GB of leftovers is STOPPED:** it needs the pool box's main-account login (see above). **2026-10-07 14:43 — `09` §3 decision 173: leave the ~2.7 GB of old leftovers for now; re-ranked P2 → P4.** Option A itself is delivered (hub 0.142.0). What stays open is ONLY the leftovers: they were computed (box total minus live customers), never listed, and deleting them needs the pool box's MAIN login, which reaches every customer's data. **Blocker: a safe main-account path.** | — | **Ruling from the run (three parts, deliberately separate):** (1) because RESET destroys custody, the ciphertext it leaves behind is unrecoverable **BY DESIGN** → RESET gains a **main-account purge of the customer base dir** (the existing operator ack already covers it); (2) the **move-aside guard STAYS** for reinstall-*without*-RESET — there custody survives and the card's "history recoverable" promise is true (R-26 depends on exactly that); (3) the operator **Restic tab shows per-customer directory bytes vs attributed snapshot bytes**, so dead data cannot hide. Measured on the pool box that night: **49 M attributed** (2 snapshots, 48.717 MiB) against **1.4 G + 3.0 M unattributed** across TWO `.orphaned-*` dirs. Evidence `restic-and-pool.txt` | CC |
| **R-1087** | Backup & restore | P4 | **Whole-box copies left on the OLD local target are never pruned after the target moves.** MEASURED 2026-10-15 (storage-identity build B space check): the N100's host root (`local`) holds three copies from 2026-07-28 (5.5–5.7 GB each, ≈ 16.8 GB); its agent's `local_backup_target` is now `felhom-backup` (the USB disk, retention 3), and retention prunes only the current target. On a small-root box such leftovers would starve the local copies (R-548). Evidence: `audits/storage-identity-buildB-2026-10-15/space/felhom-pve-9201.txt`. | OPEN | — | Decide whether the agent prunes its own `vzdump-lxc-<vmid>-*` files on a former target (after a newer copy exists on the new one), or the target move asks; until then, the N100's three July files can be removed by hand with the operator's word. | CC |
## Storage & devices — 13 rows (P3 6, P4 7)
## Storage & devices — 12 rows (P3 6, P4 6)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -209,17 +209,13 @@ stopping line that lies.
| **R-1076** | Storage & devices | P4 | **A cheap SSD reports a vendor-less WWN, so a WWN-first disk key may not be unique.** MEASURED 2026-10-15 on the N100's system SSD (AirDisk 512 GB): `ID_WWN=0x5000000123456f1f` — NAA-5 with OUI `000000`, a pattern that looks like a placeholder; its serial `QDF922W009654S30EX` looks real. The agent's wipe binding prefers `wwn-` links (A/internal/storage/durable_device.go:32). Not known whether two units share the WWN. Evidence: `audits/storage-identity-2026-10-15/B-n100.txt`. | OPEN | — | Check a second unit of the same model (or vendor data); if shared, make the agent skip OUI-000000 WWNs (the design already keys on model+serial, `architecture/16-storage-physical-identity.md` §3). | CC |
| **R-1077** | Storage & devices | P4 | **No USB enclosure that hides or fakes a disk's serial has been measured.** The storage-identity spike (2026-10-15) had none at hand and found none in earlier evidence; the design's `usb_bridge` unknown rule (`architecture/16-storage-physical-identity.md` §3) is therefore an assumption. Cheap single- and dual-bay bridges are known to report one bridge serial for any disk. Evidence: `audits/storage-identity-2026-10-15/README.md` case 4b. | OPEN | — | Buy or borrow one cheap USB enclosure (single and dual bay), run `tools/physdisk_probe.py` against it, and set the rule from what it reports. | Operator (a device) then CC |
| **R-1094** | Storage & devices | P4 | **The whole-disk format refusal tells the household to format the free part — which is refused while the other part is in use.** SEEN 2026-10-16 (controller 0.318.0): 409 „Ezen a lemezen egy használatban lévő meghajtó van, ezért a teljes lemez nem formázható. Csak a szabad részét formázhatod." — since agent 0.158.1 a part next to a mounted part can only be USED (the root wrapper trusts no „Felhom's own" claim). Evidence: `audits/storage-identity-buildB-2026-10-15/live/14-samedisk-refusal-offer.txt`. | READY | — | Reword `err.web.meghajto_hasznalatban_formazas` (hu + en): „… nem formázható. A szabad részét használatba veheted." Next controller release; the copy gates run. | CC |
| **R-1109** | Storage & devices | P4 | **A change that alters only `smb.conf` (not the compose file — e.g. a share renamed at the same path) is never applied to a running samba container.** `smb.conf` is a bind-mounted FILE written by atomic rename, so a running container keeps the old inode; `reconcileSambaAt` runs `compose up -d`, which recreates only when the COMPOSE changed. Read from code 2026-10-16 night while fixing R-1108 (the helper session that wrote that fix). | OPEN — filed 2026-10-17 | — | Recreate when `smb.conf` changed (`--force-recreate`, or a config hash label in the compose); a test with a same-path rename | CC |
## Security & access — 21 rows (P1 1, P2 2, P3 11, P4 7)
## Security & access — 18 rows (P1 1, P2 2, P3 9, P4 6)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-985** | Security & access | P3 | **Our zero-knowledge wording is stronger than the product: on a live box the operator can reach the off-site backup key (through the vaulted root credential and the operator tunnel) without the customer's recovery code.** Design-shaped; decision for the operator in `STATUS.md`. Fix options: change the wording, or remove operator root reach to the key. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | WAITING-ON-OPERATOR — design-proposal | operator decision | Decide the wording or the change | operator |
| **R-977** | Security & access | P3 | **FileBrowser's default password can come back if its data volume is ever re-created, because the box checks it only once; and the tunnel can start before the box is claimed.** Static; needs a live check on 9202. Fix: check at every start, and do not start the tunnel before the claim. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Measure on 9202; then fix | CC |
| **R-1113** | Security & access | P4 | **`/__family/start` with a household dashboard session creates a NEW 30-day household family session on every pass, so `family.json` keeps growing.** Found by the R-976 helper session 2026-10-16 night (read from code). | OPEN — filed 2026-10-17 | — | Reuse the household's live family session (or bound the list); a test that N passes leave one session | CC |
| **R-1112** | Security & access | P3 | **Family sessions are stored in `family.json` as the raw session value, not as a fingerprint the way dashboard sessions are (R-35)** — anyone who reads the controller's data folder can lift a family session. Found by the R-976 helper session 2026-10-16 night. | OPEN — filed 2026-10-17 | — | Store a fingerprint (the R-35 pattern); a test that the file holds no usable value | CC |
| **R-1111** | Security & access | P3 | **The share-password limiter (`/s/<token>`, `shareRateLimited`) and the escrow re-auth limiter (`escrowRateLimited`) still count a try only AFTER the slow password check, so parallel tries get past them** — the shape v0.324.0 fixed for the dashboard and family sign-in (R-983, „reserve before compare"). Found by the R-983 helper session 2026-10-16 night. | OPEN — filed 2026-10-17 | — | The same reserve-before-compare pattern (REUSE.md) on both, with a parallel test each | CC |
| **R-954** | Security & access | P2 | **We cannot tell whether anyone used the old hub operator key (R-950) or the open hub read routes (R-951) before 2026-10-12; if someone did, they could have read every box's break-glass root credential and every customer's box report.** The exposure began in February 2026; the logs that could show it cover only 2026-10-12 from about 09:54 and one day in June, so about 238 days are not visible. Fix (operator decision): change the break-glass root password of every box (tester-1 holds a real person's data), or accept the risk; either way, keep ingress access logs long enough to answer this next time. | WAITING-ON-OPERATOR — filed 2026-10-12 by the security review; the log search was not completed in-session | operator decision | Decide: rotate every box's break-glass root password now, or accept | operator |
| **R-1042** | Security & access | P3 | **The boxes' Cloudflare keys cannot change zone settings, so Cloudflare's own „Always Use HTTPS” cannot be switched on.** Measured 2026-10-14 on all three zones (`audits/https-only-2026-10-14/``1A-1-zones-and-keys.txt`): each `cf_api_token` is valid and sees only its own zone, but GET/PATCH `settings/always_use_https`, `ssl`, `security_header` answer 403; the redirect and transform rule phases too. The https-only fix therefore lives on the box (decision 246). A second layer at Cloudflare needs „Zone Settings: Edit” (and, for headers, „Transform Rules”) added to each customer's key — the hub's zone-reach check stays. | **WAITING-ON-OPERATOR** — filed 2026-10-14 | a key permission only the operator can grant | Optional: widen each customer key, then switch „Always Use HTTPS” on per zone | operator |
| **R-1104** | Security & access | P3 | **Check https-only on Tester 2 when it reports again.** R-1033 (plain http on every public name) is closed on the three boxes that report (35 names → 301, 2026-10-16). Tester 2 (`sajatfelhom.hu`) has been off since 2026-10-04 on controller 0.292.0; its addresses answer Cloudflare 530 (tunnel down), so nothing is served in plain http while it is off. When it comes back it takes the floor (≥ 0.312.0 carries the redirect). | OPEN — filed 2026-10-16 | Tester 2 reporting again | Run `scripts/https_only_check.py` with Tester 2 in it; close | CC |
@@ -309,17 +305,14 @@ stopping line that lies.
| **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator |
| **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC |
## Process & tooling — 31 rows (P3 11, P4 20)
## Process & tooling — 29 rows (P3 10, P4 19)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1083** | Process & tooling | P3 | **The scratch box 9202 cannot use any feature that needs the agent: its `bootstrap.json` has no `local_api` block, so its controller never gets an agent client.** MEASURED 2026-10-15 (storage-identity build A): 9202 put on controller 0.317.0 by hand to show that a NAS share no longer hides the one-copy notice (R-1075); its `bootstrap.json` (read on the box) holds no `local_api`, so the controller has no agent to ask and keeps the old `st_dev` rule — the check could not tell old from new — 9202 was put back on 0.314.0. Every agent-coupled feature (drives, same-disk, format, network storage) is untestable on the scratch box today, and the NAS leg of build A is proven by a red-proofed unit test only. Evidence: `audits/storage-identity-buildA-2026-10-15/9202-upgrade.txt`. | OPEN | — | Give 9202 a local-API token the normal way (the agent mints it for its guest), or record in `runbooks/target-selection.md` that 9202 is controller-only; then show the NAS notice live. | CC |
| **R-1081** | Process & tooling | P4 | **Two published controller tags hold content that is not what their number says: `0.315.0` and `0.316.3`.** Both were caused by CC on 2026-10-15, and both by the same shape — a guard that was not on the path the work takes. **`0.316.3`:** the build directory's `build.sh` is a symlink into the SHARED clone's working copy, which predated the `FELHOM_REPO_DIR` override, so the override was silently ignored and the build copied that tree — which held 31 uncommitted files of another session's physical-disk storage work. The banner printed `Commit: 0225255` with no `Repo:` line. **Nobody deployed it; that was luck, not a control.** Fixed at the root in controller v0.316.4: `build.sh` now refuses `--push` from a tree that is dirty, not at `origin/main`, or not a git checkout, with four test cases red-proved. **Neither tag is re-pushed or deleted** — a version number is pushed once, and that applies to numbers CC spoiled. **No box runs either:** demo-hp 9201 is on 0.317.0, the floor is 0.314.0, and the button offers the highest tag. **`0.315.0`:** Caused by CC on 2026-10-15 while proving R-1080's own fix: the guard was added to `controller/build.sh` in the repo, but the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a **separate copy taken 2026-07-18** and had no guard. Running the refusal against a real existing tag therefore rebuilt and re-pushed 0.315.0 — a fourth time — with v0.316.0's code under it (digest `8887343011d6` → `b809201841f7`). **Fixed at the root in the same session:** the build directory's script is now a symlink to the repo's, the old copy kept beside it as `build.sh.stale-copy-2026-07-18`, and the refusal was then proven to fire from the documented path. **No box is affected:** nothing runs 0.315.0 (demo-hp 9201 is on 0.316.2), the floor is 0.314.0, and the household button offers the highest tag. What remains is two tags that must never be deployed and are no longer reproducible from the registry. | **OPEN** — filed 2026-10-15; nothing to do unless someone needs one of those two images, and then the answer is the current release | — | Leave them. If an audit ever needs the real 0.315.0 or 0.316.3, build it from the matching commit under a NEW number and say so | CC |
| **R-992** | Process & tooling | P3 | **The drill-catalog route that `09` §6.5 documents cannot be followed: `admin/app-catalog-drill` is PRIVATE and no box has catalog credentials.** Measured 2026-10-12 while publishing the Usenet pair: the box's clone fails with `fatal: could not read Username for 'https://gitea.dooplex.hu'` (exit 128), the Gitea API reports the drill repo `private=true` and the live catalog `private=false`, and NEITHER the current controller.yaml on 9202 NOR any of its 30 saved copies has ever carried `git.username` or `git.token` (the controller supports both — `internal/config/config.go` GitConfig). The route worked for the campaigns of 2026-10-10 and the torrent pair earlier the same day, so the repository's visibility changed between then and now. **Worked around, not fixed:** that campaign staged its two templates in the box's own catalog cache (`audits/usenet-2026-10-12/tools/stage_in_cache.py`), which is where the sync copies a catalog change anyway, with the sync interval raised to 12h for the duration and rescan used in place of sync. The real fix is an operator decision: either a read-only catalog credential on the scratch box (a secret on a Tier-0 guest) or making the drill repository readable. Until then every new-app campaign hits this. | WAITING-ON-OPERATOR | the operator's choice between a box credential and a readable drill repo | Decide; then either set `git.username`/`git.token` on 9202 from the credentials store, or flip the drill repo, and correct `09` §6.5 either way | operator |
| **R-1110** | Process & tooling | P4 | **The R-1017 guard covers `UserdataDir` only; two sibling path helpers are still called with drive-named arguments whose comments claim a namespace root:** `stacks/delete.go` `RecoveryUnitPath(drivePath, name)` and `web/handlers.go` `backup.AppDataDir(storagePath, name)`. The guard `TestNoBareDrivePathToUserdataDir` found one real defect of this shape tonight (`userdataKept`, fixed in v0.324.0). | OPEN — filed 2026-10-17 | — | Extend the guard to `RecoveryUnitPath`/`AppDataDir`/`PrimaryBackupPath`, fix what it finds | CC |
| **R-1114** | Process & tooling | P4 | **`controller_gates.py` cannot pass inside a worktree outside `git/` (e.g. `/mnt/5_hdd/felhom.eu/wt/<name>`): the reuse-refs, instructions and observations gates look for felhom.eu only at `../felhom.eu`, report MISSING, and the runner fails.** Measured 2026-10-16 night by both helper sessions (each ran the four gates by hand from felhom.eu). | OPEN — filed 2026-10-17 | — | Resolve the sibling from `FELHOM_SHARED_SCRIPTS` or the main clone (`git worktree list`), as retrieval-promise already does | CC |
| **R-1115** | Process & tooling | P3 | **The catalog decoy suite fails one case: „FACT: a credential token rewritten in translation" — `check-copy-i18n.py` answers rc 0 where the decoy expects 1, so the gate may no longer convict a reworded credential token.** Measured 2026-10-16 night (`python3 scripts/test_gate_decoys.py`, with and without the new `db-credentials` cases — the same failure both ways). `catalog_gates.py --fast` is green because the decoy suite is not one of its gates. | OPEN — filed 2026-10-17 | — | Find whether the gate or the decoy moved; red-prove the gate on the case again | CC |
| **R-1022** | Process & tooling | P3 | **Tester 1 follows the drill catalog and carries two test drives — teardown owed (operator ruling (e), 2026-10-14).** Steps, only on the operator's word: (1) remove the test apps (Sonarr, Radarr, qBittorrent, Transmission, Plex, Jellyfin) through the dashboard WITHOUT „delete data” (R-1027); (2) decommission Media A and Media B through Tester 1's dashboard; (3) `repoint.py restore` — `controller.yaml` from `controller.yaml.pre-media-2026-10-14`, read back byte-identical, `repo_url` the live catalog; remove `drill-catalog.git` from the controller volume; (4) on demo-hp: `qm set 341 --delete scsi2,scsi3`, then delete the two volumes BY NAME (`nvme-scratch:341/vm-341-disk-3.raw`, `-disk-4.raw`) — never `qm destroy`; (5) VM memory unchanged (8192, not raised); (6) drill repo reset to live `main`. Tools: `audits/media-all-drives-2026-10-14/``tools/`. **Added 2026-10-14 night:** (4b) Media C — `qm set 341 --delete scsi4`, then the volume `nvme-scratch:341/vm-341-disk-5.raw` by name (decommission it on the dashboard first; it holds 4 test files); (7) DONE 2026-10-15: `plex-k6` and its volume removed after the claim (0 left) — the operator removes its server entry „plex-k6” from plex.tv (Settings → Authorized Devices); (8) drill commit `925f3fd` (media_c binds) goes with step 6; Prowlarr and the test drive tag „driva-a” go with step 1. Tools also in `audits/media-drive-events-2026-10-14/tools/`. **Added 2026-10-15 (media build):** (4c) Media D — decommission it on the dashboard, then `qm set 341 --delete scsi5` and the volume `nvme-scratch:341/vm-341-disk-6.raw` by name (20 GB; 2 own-bytes torrents on it); (9) Tester 1 runs controller `0.313.0-rc.3/rc.4` by hand — put the vouched release back (`/etc/felhom-controller-image`); (10) the rig's Sonarr/Radarr hold two sets of drive tags and clients (the night script's `drive-a…` and the product's `media-a…`) — they go with step 1. | **WAITING-ON-OPERATOR** — filed 2026-10-14 | the operator's teardown word | Run the six steps; quote each read-back | CC |
| **R-1022** | Process & tooling | P3 | **Tester 1 follows the drill catalog and carries two test drives — teardown owed (operator ruling (e), 2026-10-14).** Steps, only on the operator's word: (1) remove the test apps (Sonarr, Radarr, qBittorrent, Transmission, Plex, Jellyfin) through the dashboard WITHOUT „delete data” (R-1027); (2) decommission Media A and Media B through Tester 1's dashboard; (3) `repoint.py restore` — `controller.yaml` from `controller.yaml.pre-media-2026-10-14`, read back byte-identical, `repo_url` the live catalog; remove `drill-catalog.git` from the controller volume; (4) on demo-hp: `qm set 341 --delete scsi2,scsi3`, then delete the two volumes BY NAME (`nvme-scratch:341/vm-341-disk-3.raw`, `-disk-4.raw`) — never `qm destroy`; (5) VM memory unchanged (8192, not raised); (6) drill repo reset to live `main`. Tools: `audits/media-all-drives-2026-10-14/``tools/`. **Added 2026-10-14 night:** (4b) Media C — `qm set 341 --delete scsi4`, then the volume `nvme-scratch:341/vm-341-disk-5.raw` by name (decommission it on the dashboard first; it holds 4 test files); (7) DONE 2026-10-15: `plex-k6` and its volume removed after the claim (0 left) — the operator removes its server entry „plex-k6” from plex.tv (Settings → Authorized Devices); (8) drill commit `925f3fd` (media_c binds) goes with step 6; Prowlarr and the test drive tag „driva-a” go with step 1. Tools also in `audits/media-drive-events-2026-10-14/tools/`. **Added 2026-10-15 (media build):** (4c) Media D — decommission it on the dashboard, then `qm set 341 --delete scsi5` and the volume `nvme-scratch:341/vm-341-disk-6.raw` by name (20 GB; 2 own-bytes torrents on it); (9) Tester 1 runs controller `0.313.0-rc.3/rc.4` by hand — put the vouched release back (`/etc/felhom-controller-image`); (10) the rig's Sonarr/Radarr hold two sets of drive tags and clients (the night script's `drive-a…` and the product's `media-a…`) — they go with step 1. | **WAITING-ON-OPERATOR** — filed 2026-10-14 2026-10-17 (burndown 3): the drill repo's `main` is now live main + one docmost commit (`9bad48a`, `daff3d2`); the DRILL prowlarr commits stay in its history — re-mirroring Tester 1 would carry that tree. Tester 1 therefore did not take catalog `5d9450b` (Redis RDB). | the operator's teardown word | Run the six steps; quote each read-back | CC |
| **R-1034** | Process & tooling | P3 | **Gate 17 checks the FAQ JSON-LD on two pages only, by a hard-coded list — a FAQPage anywhere else is unchecked.** `scripts/site_gates.py` gate 17 loops `for p in ("gyik.html", os.path.join("en", "faq.html"))`, so its „JSON-LD equals the visible questions and answers” guarantee stops at those two files. **Since 2026-10-14 two more pages carry a FAQPage block the gate never reads:** `/sajat-felho-tarhely` and `/en/own-cloud-storage`. Their JSON-LD was *derived* from the visible headings and paragraphs by a script and then verified verbatim against the rendered text, so it is correct today — but nothing stops the next copy edit moving the visible text and leaving the structured data behind, which is exactly the failure gate 17 exists to catch, and the one Google penalises. **Why it is not a one-line fix:** gate 17's extractor is written for the FAQ pages' accordion markup (`<button class="faq-question">` … `<div class="faq-answer-inner">`); the new pages use plain `<h2>` + `<h3>`/`<p>` sections, so widening the scope needs a second extractor beside the first. **Scope is a fact** (repo `CLAUDE.md`: prefer a walk over a hand-maintained list) — the gate should scan **every page containing a FAQPage block**, not a named pair, or the next page to carry one is unguarded in the same way. A decoy is owed for the widened gate (R-421). | **OPEN** | — | widen gate 17 to every page carrying a FAQPage block, second extractor + decoy | CC |
| **R-987** | Process & tooling | P4 | **The 53 catalog app images have never been scanned for known vulnerabilities.** Fix: a monthly trivy pass over the images the catalog pins, reported, not gating. | OPEN | — | Add to the monthly re-test | CC |
| **R-986** | Process & tooling | P3 | **No dependency or secret scanner runs on any push, so a known-vulnerable library or a committed key is found only by a review like this one.** Proposed gate: govulncheck (called vulns only), gitleaks on the diff, trivy on our own images; start as WARN with an accepted-hits file, then FAIL. Tools and pinned versions: `audits/security-review-2026-10-12/SCANNERS.md`. Note: gitleaks' default rules did not find R-908's key. | OPEN | — | Build the gate, warn-first | CC |
@@ -344,6 +337,7 @@ stopping line that lies.
| **R-1025** | Process & tooling | P4 | **A power drill on demo-hp cuts Tester 1 too, and nothing warns the session using it.** 2026-10-14 17:25 CEST another session cut demo-hp's plug (`plugcut.py --plug hp`, the Immich drill) while this session ran downloads on Tester 1 (VM 341 runs on demo-hp); Tester 1 rebooted and came back by itself. The drill's fence checks Tester 1 for backups and package jobs only. | **OPEN** — filed 2026-10-14 | — | Add „a test runs on Tester 1” to the fence (a marker file the testing session writes), or name the rule in `runbooks/target-selection.md` | CC |
| **R-1039** | Process & tooling | P4 | **qBittorrent stalls on the LAST piece of an archive.org torrent; Transmission does not.** Seen three times on 2026-10-14 (Tester 1 S2 and S3, demo-hp 9201 live read-back): the film reaches 99.994 %, the piece shared with archive.org's side files (`__ia_thumb.jpg`, `_meta.sqlite`, `_meta.xml`) never arrives, qBittorrent's log shows „500 Internal Server Error” from the archive.org web seed, while Transmission finishes the same torrent and curl range requests on the same files answer 206. A household that uses archive.org torrents with qBittorrent hits it too; our tests finish only with a local peer. | **OPEN** — filed 2026-10-14 | — | Note it in qBittorrent's onboarding record; for test films use Transmission, or a public-domain torrent with real peers | CC |
| **R-1078** | Process & tooling | P4 | **A controller comment still describes `mp1 /mnt/sys_drive` as a separate volume.** `C/cmd/controller/main.go:2546` („the SYSTEM-DATA volume (`mp1`, `/mnt/sys_drive`)…”) — `mp1` was removed by R-165; `sys_drive` is a folder of `mp0` (measured on all three boxes, `audits/storage-identity-2026-10-15/README.md`). The same stale cell in `07-backup-architecture.md` §6.1 was corrected in this session. Not fixed here: this run allowed no product-repo change. | READY | — | Correct the comment in the next controller change. | CC |
| **R-1116** | Process & tooling | P4 | **The controller, agent and felhom.eu gate runners do not run their own decoy suites — the shape that let R-1115 fail unseen for a night.** The catalog's runs since 2026-10-17 (`gate-decoys`, full run). Measured today: the controller's suite is green (53 decoys). Found by burndown 3. | OPEN — filed 2026-10-17 | — | Add each repo's `test_gate_decoys.py` as a runner row (fast if it is quick, else the full run), with the count test updated | CC |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.