Commit Graph

402 Commits

Author SHA1 Message Date
admin 7f9a93c0f2 paperless-ngx: remove the client-written X-Forwarded-For chain on its router (R-753)
django-allauth 65.12.1 — leftmost XFF for its 10/min per-IP login limit. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:03:01 +02:00
admin ba06d488db outline: remove the client-written X-Forwarded-For chain on its router (R-753)
Koa proxy — leftmost XFF for its per-IP limits and the sign-in link's IP binding. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:03:00 +02:00
admin 9395d19333 emby: remove the client-written X-Forwarded-For chain on its router (R-753)
leftmost XFF decides who is on the LAN (remote-access and IP-filter bypass). Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:58 +02:00
admin 95cb9bbfa7 romm: remove the client-written X-Forwarded-For chain on its router (R-753)
uvicorn --forwarded-allow-ips=* — leftmost XFF for its pair-code limits. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:57 +02:00
admin 460f75e45e sparkyfitness: remove the client-written X-Forwarded-For chain on its router (R-753)
better-auth — leftmost XFF for its per-IP sign-in limit. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:56 +02:00
admin 828fafe7d7 adventurelog: remove the client-written X-Forwarded-For chain on its router (R-753)
django-allauth 0.63.3 — leftmost XFF for its per-IP login limits. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:54 +02:00
admin 8975984b6e ghost: remove the client-written X-Forwarded-For chain on its router (R-753)
Express trust proxy true — leftmost XFF; its brute-force buckets are keyed by IP (+username). Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:53 +02:00
admin 22fa01b716 audiobookshelf: remove the client-written X-Forwarded-For chain on its router (R-753)
request-ip — leftmost XFF; its auth limiter (40/10 min) is keyed by IP only. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:52 +02:00
admin 04e951697c docmost: remove the client-written X-Forwarded-For chain on its router (R-753)
Fastify trustProxy true — leftmost XFF; its login limit (10/min) is keyed by IP only. Once traefik trusts the tunnel's fixed address (controller v0.286.0), the leftmost entry is what a
stranger writes; with the chain removed the app reads traefik's X-Real-Ip or its peer, as before — never forgeable.
Measured on 9202: a router with this middleware receives no X-Forwarded-For (felhom.eu audits/visitors-2026-10-01/A/P1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 21:02:51 +02:00
admin 94477cba43 Grimmory tested but held (R-775): its fixture added; CHANGELOG, CONTEXT, REPORT for the night's new apps
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 19:51:12 +02:00
admin 72247a387e Dawarich: the third new app through the checklist — template, record, fixture, first ladder step 1.15.2 -> 1.15.3
gates / gates (push) Successful in 3s
Location history with PostGIS 17 + Redis + Sidekiq. The seeded known login replaced by after_install behind the install
hold; geocoding off; SECRET_KEY_BASE a data_key; smtp_mapping with mail-off boot measured. onboarding/dawarich.md complete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 18:37:05 +02:00
admin 882ac14309 Karakeep: the second new app through the checklist — template, record, fixture, first ladder step 0.33.1 -> 0.33.2
gates / gates (push) Successful in 2s
Bookmarks/articles/notes with a crawler (karakeep-chrome) and search (meilisearch v1.41.0). AI off unless the household
enters a key; setup gate + sign-up closed twice; Chrome healthcheck over bash /dev/tcp; smtp_mapping (plaintext), mail-off
boot measured; web memory 768M -> 1536M on measurements (bench watch, box crawl burst). onboarding/karakeep.md complete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 18:01:07 +02:00
admin 195129cbb1 Radicale: the first new app through the checklist — template, record, fixture, first ladder step 3.8.0 -> 3.8.1
gates / gates (push) Successful in 2s
Calendar and contacts (CalDAV/CardDAV), ghcr.io/kozea/radicale:3.8.1. Login file written from the generated password on
the first start only (R-765: rewriting it at every start lost the household's login on a restore). onboarding/radicale.md
complete; bench + 9202 proven; FIRST-ADMIN, README, Hungarian freeze.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 16:58:56 +02:00
admin 55b8c8a147 wger: lifecycle hidden until R-762 and R-763 are fixed (operator ruling 2026-10-01)
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 16:09:40 +02:00
admin dc0ab8b2a8 New-app checklist: reviewed, a gate (onboarding), the wger pilot record, the existing apps' gap page
gates / gates (push) Successful in 2s
NEW-APP-CHECKLIST.md: the reviewer's draft reviewed - 60 rows in 10 groups, each with how/why and a since date;
7 rows added, 16 sharpened, 9 wrong claims fixed. onboarding/_TEMPLATE.md (one line per id), onboarding/wger.md
(the pilot, exempt app, 11 open rows each a register row), onboarding/EXISTING-APPS-GAPS.md (read only, from
scripts/onboarding_gaps.py). Gate onboarding (scripts/check-onboarding.py) in --fast: a template directory not
among the 53 published before 2026-10-01 needs a complete record; decoys in test_gate_decoys.py (16 cases, 5 gate
mutants seen red). CLAUDE.md, REUSE.md 5, README point to it. No template changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 15:20:52 +02:00
admin 6d72c091e0 Merge branch 'main' of https://gitea.dooplex.hu/admin/app-catalog-felhom.eu
gates / gates (push) Successful in 2s
2026-10-01 14:49:19 +02:00
admin 6f18f74e6b new app checklist 2026-10-01 14:48:25 +02:00
admin 9c5eae9999 CHANGELOG + REPORT: calibre-web generated login name (decision 61); an installed app's template is not frozen (R-757)
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 13:29:50 +02:00
admin e9f50b5496 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
gates / gates (push) Successful in 2s
after_install renames admin to the generated ADMIN_USER in app.db, sets the password with cps.py -s, and proves both
before its success line. Proven on 9202: 40 wrong tries on admin, the household still in at once (form and OPDS).
Hungarian freeze re-captured for the five changed calibre-web strings only.
Evidence: felhom.eu/documentation/audits/calibre-name-and-prune-2026-10-01/A/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 13:19:02 +02:00
admin ed6df4b46b CHANGELOG + REPORT: wger lockout fix, three apps measured (R-752)
gates / gates (push) Successful in 3s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 12:49:39 +02:00
admin 82fff329a4 wger: a stranger locks only the name they try, for 5 min — not every household member for 30 (R-752, 09 decision 58 — decided by CC unattended, operator may reverse)
gates / gates (push) Successful in 2s
django-axes keyed on ip_address, and behind the tunnel every visitor has the tunnel container's address (R-753).
Measured on 9202 (felhom.eu/documentation/audits/lockouts-2026-10-01/B/): live template — 10 wrong tries on admin
locked the second member too; with AXES_LOCKOUT_PARAMETERS=username, AXES_COOLOFF_TIME=5 and the database handler —
the second member unaffected, admin in again at 7.5 min after one retry during the lock, a wrong password still
refused. Settings only: no image moves, no ladder entry (gates OK).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 12:46:58 +02:00
admin 83636352ea REPORT: the first full monthly re-test, mealie's lockout, R-744/R-746/R-749
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 10:21:00 +02:00
admin a4597cd005 mealie: lockout 1 hour instead of 24 (R-747, 09 decision 57 — decided by CC unattended, operator may reverse)
gates / gates (push) Successful in 2s
mealie locks the ACCOUNT after 5 wrong logins and its login names (admin, changeme@example.com) are public, so a
stranger could lock the household out for a day. Measured on 9202 with SECURITY_USER_LOCKOUT_TIME=1: the right
password answered 423 for 120 min (the hourly job lifts it after the hour), then 200; a wrong one still 401.
Evidence: felhom.eu/documentation/audits/rulings-2026-10-01/C/. CHANGELOG also records today's re-test run and fixes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 10:18:17 +02:00
admin 9fc705209d outline fixture: read Outline 1.10's __Host-csrfToken cookie (R-744)
gates / gates (push) Successful in 2s
Outline 1.10 names the CSRF cookie __Host-csrfToken on a secure request and csrfToken over plain HTTP (server/utils/
csrf.ts getCookieName); 1.9.1 always said csrfToken. Proven on 9202 at the live pin 1.10.1: installation.create 302,
cookie __Host-csrfToken, apiKeys.create, a published document read back, unknown id and wrong key refused
(felhom.eu/documentation/audits/rulings-2026-10-01/D/D2-outline-fixture-9202.txt). The old pattern cannot match that
header line (the red: 2026-09-30, both venues).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 08:19:19 +02:00
admin 804884a749 image_digest.resolve honours a @digest: asks the registry for THAT manifest (R-746)
gates / gates (push) Successful in 2s
The digest was dropped and the tag's current digest returned, so 'is this digest still served' got a false yes
(measured: redis:7-alpine@sha256:000…0 resolved to the tag's digest; now HTTP 404). A malformed digest is refused
without a request. test_image_digest.py (no network); red-proof: the pre-fix resolver fails 3 of 4 cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 07:54:39 +02:00
admin 1a37032f99 sonarr: re-test of the same tag at a new digest (decision 52, R-740)
gates / gates (push) Successful in 2s
STEP sonarr: the superseded step {'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'} keeps its definition at steps/bf0bfeab9db53720.yml
STEP sonarr: … and its .felhom.yml at steps/bf0bfeab9db53720.felhom.yml
WROTE sonarr: RE-TEST {'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'} -> {'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'} peak 13.9% marks {'files_may_change': False, 'needs_person': None, 'memory_tight': False} digest {'sonarr': 'sha256:a5c1a5fecbef946927ab90ad68df319ac5fe644057e5fc18cd993f01ac07b2b2'} -> {'sonarr': 'sha256:f247545d23ba8b233d6604575347e48a623fe6ad75dda02348bf81917f3b5c06'}

Evidence: felhom.eu/documentation/audits/retest-2026-10/sonarr
2026-10-01 07:53:44 +02:00
admin 3b59dfb1bc nextcloud: re-test of the same tag at a new digest (decision 52, R-740)
gates / gates (push) Successful in 2s
STEP nextcloud: the superseded step {'nextcloud': 'nextcloud:34.0.4-apache', 'nextcloud-db': 'mariadb:12.3', 'nextcloud-redis': 'redis:7-alpine'} keeps its definition at steps/61e93c3e1a1806df.yml
STEP nextcloud: … and its .felhom.yml at steps/61e93c3e1a1806df.felhom.yml
WROTE nextcloud: RE-TEST {'nextcloud': 'nextcloud:34.0.4-apache', 'nextcloud-db': 'mariadb:12.3', 'nextcloud-redis': 'redis:7-alpine'} -> {'nextcloud': 'nextcloud:34.0.4-apache', 'nextcloud-db': 'mariadb:12.3', 'nextcloud-redis': 'redis:7-alpine'} peak 23.7% marks {'files_may_change': False, 'needs_person': None, 'memory_tight': False} digest {'nextcloud': 'sha256:a5ace30c695afe48c2c406e940ee7886a81e13fa382e57cd68b2416d1a66914c', 'nextcloud-db': 'sha256:805c8e104bd563d5bfa24fadd3f31cd419ea859cb5277f32b5dbf2db714f9ed1', 'nextcloud-redis': 'sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499'} -> {'nextcloud': 'sha256:37b109885aa3cba3e056362a899556a625c986f2c17cd2c0cc157d21c789f53b', 'nextcloud-db': 'sha256:805c8e104bd563d5bfa24fadd3f31cd419ea859cb5277f32b5dbf2db714f9ed1', 'nextcloud-redis': 'sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499'}

Evidence: felhom.eu/documentation/audits/retest-2026-10/nextcloud
2026-10-01 07:37:51 +02:00
admin 9e53205938 retest-floating: check what the bench brings (docker, python3), not what the sync puts there (R-749)
gates / gates (push) Successful in 1s
The check asked for /opt/upg/upgrade-test.py before sync_bench() copies it, so a bench freshly created by the
runbook was refused in one minute (2026-10-01). After the sync, the file is now required.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 07:17:49 +02:00
admin efd492d0bb CHANGELOG + REPORT: same-tag re-tests (decision 52), wger's key, wanderer on the bench
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 23:23:16 +02:00
admin 6a3ead9ebe Same-tag security fixes as tested steps (09 decision 52, R-740): re-test entries, their gates, the monthly command
gates / gates (push) Successful in 2s
- A RE-TEST entry: from == to, digest = the registry's new digest, digest_from = the tested one, box_evidence.
  ladder.check_entry refuses one with no new digest, no digest_from or no box proof; check-test-record rule 2b
  ties digest_from to the previous entry's digest; check-test-record-move now judges re-tests too (they change
  .felhom.yml only — the gate looked at compose moves alone) and refuses a digest the registry no longer serves.
  Decoys: 8 cases in test_gate_decoys.py, seen red with the rules switched off.
- upgrade-test.py --retest <app> [svc]: FROM the ladder head's tested digest TO the registry's current one, the
  full method; --write-ladder writes a re-test entry (plain refs + digest_from), refusing without the box venue or
  when the registry moved again. Writer tests, red-proofed.
- scripts/retest-floating.py — ONE command: --dry-run lists, --engines-only is the ruled start; bench, box
  (retest_box.py on 9202 via the drill catalog), writer, gates, one commit per app. box_walk.py moves the box
  client into the catalog. Run today: nothing to re-test on the database/redis lines.
- End to end on 9202 (drill): docmost at the OLD redis digest, the re-test, "run tonight's chain now" -> the leg
  pressed it, the new digest runs, read back, badge current.
- Also: upgrade-test.py BENCH_ENV_OVERRIDES (R-739, wanderer's DB address on the bench, recorded per verdict);
  test_gate_decoys.py read kimai's tag and date from the clone (red on main since kimai moved).

Evidence: felhom.eu/documentation/audits/night-rulings-2026-09-30/A/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 23:06:09 +02:00
admin 45d84827ad wger: its app login API gets its JWT key pair (R-737)
gates / gates (push) Successful in 2s
The template set no JWT_PRIVATE_KEY/JWT_PUBLIC_KEY, so the app login (the mobile app's route) answered 500
on a CORRECT password. The deploy's generators cannot make an RSA pair, so the start command makes it ONCE
with wger's own `manage.py generate-jwt-keys`, keeps it 0600 on wger's data volume (a restore brings it
back), and loads it before the image's own entrypoint. Measured on the bench (2.7): right password 200 with
an access token that reads the API (200); wrong password 400 (allauth's answer); the key survives a
restart. No image moves.

Evidence: felhom.eu/documentation/audits/night-rulings-2026-09-30/D/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 22:27:13 +02:00
admin d18116539a CHANGELOG + REPORT: twelve more steps, six first ladders (R-462, R-738, R-742, R-732)
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 20:26:54 +02:00
admin fb8703020e zipline: 4.7.0 -> 4.8.0, its second ladder step, both venues proven (R-462, R-742)
gates / gates (push) Successful in 2s
The route 4.8.0 itself asks for (its prisma -> drizzle migration needs 4.7.x first). Bench 9401
(harness v4, swap 0; two earlier attempts stopped at the FROM pull because the bench's own disk was
full — images removed by name, no prune): the user logs in before and after, 10-min watch 0 kills
(anon peak 34.3 %); the abort starts and serves. Box 9202: done in 32.8 s, the user logs in.
4.7.0 keeps its definition in steps/. Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 20:22:53 +02:00
admin a9700e2b4c zipline: 4.6.1 -> 4.7.0, its first ladder step, both venues proven (R-462, R-742)
gates / gates (push) Successful in 2s
4.8.0 cannot be reached in one step: it refuses to start until the database has run the release
before it (prisma -> drizzle; measured on both venues; the box undid it by itself in 20 s). So the
first step is 4.7.0. Bench 9401 (harness v4, swap 0): the first user made through /api/setup and
logging in before and after, 10-min watch 0 kills (anon peak 41.7 %); the abort starts and serves.
Box 9202: done in 103.6 s through the setup gate, the user logs in. PostgreSQL 16 does not move.
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 20:05:42 +02:00
admin 84de9a9254 harness: zipline's readback logs in with the right password first (its login limit answered 429 after the wrong-password control; R-742)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 20:05:31 +02:00
admin 48440ce01c immich: step v3.0.3 -> v3.2.2 (0b8272068aab36bf) re-proven at 768M; its definition rewritten by the writer (R-732)
gates / gates (push) Successful in 2s
That step still pinned immich-postgres at 512M. It re-runs the geodata import (v3.0.3 ships geodata
dated 2026-07-13, v3.2.2 2026-08-30 — read inside both images), the load R-732 measured at ~575 MB.
Re-proof on bench 9401, swap 0, on the step's OWN definition with only the limit changed (768M;
mem_limit 4096M -> 4480M): `Starting geodata import` ... `Geodata import completed` in 17 s, the
database's kill counter 0 from its birth, the album read back, 10-min watch 0 kills (anon peak 29.6 %).
Written by upgrade-test.py --restep; the ladder entry is untouched. No box reporting to the hub runs
immich (hub /apps, same day).

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/D/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 19:34:08 +02:00
admin 8d3a35a720 rallly: 4.11.1 -> 4.15.3, within-major step, both venues proven (R-462)
gates / gates (push) Successful in 2s
The app half only; PostgreSQL 18 does not move. Bench 9401 (harness v4, swap 0): sign-up with the
e-mail code from its own table, a poll seeded and read back before and after, 10-min watch 0 kills
(anon peak 60.6 %); putting 4.11.1 back after the migration loses the poll (recorded; the box's undo
restores the pre-update copy). Box 9202: done in 64.7 s through the setup gate, the poll read back.
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 19:18:24 +02:00
admin b0b25148a4 outline: 1.9.1 -> 1.10.1, within-major step, both venues proven (R-462)
gates / gates (push) Successful in 3s
The app half only; PostgreSQL 18 and redis 7 do not move. Bench 9401 (harness v4, swap 0): the
self-hosted workspace + a document seeded and read back before and after, 10-min watch 0 kills (anon
peak 33.5 %); the abort starts and serves. Box 9202: done in 89.2 s through the setup gate, the
document read back. Written by upgrade-test.py --write-ladder; the PG 18 step keeps its definition.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 19:14:09 +02:00
admin 63a96b0ef4 harness: --restep rewrites one superseded step's definition from a re-proof; zipline's readback waits on its health route
gates / gates (push) Successful in 2s
- upgrade-test.py --restep <verdict> --definition <dir> --catalog <c> --evidence <rel>: the only way a
  superseded step's own files (steps/<key>.yml + .felhom.yml) change after the fact (Part D: immich's
  step 0b8272068aab36bf still pins 512M). Refuses a non-proven or OOM-killing re-proof, the head entry,
  and a definition whose images are not the step's; leaves the ladder entry untouched (digests, box
  evidence, the box's failed-step fingerprint). Two tests; the gate accepts the result.
- zipline's verify waited on `/` for 200/302/307; on 9202 it answers 301 and the readback returned
  False with no line — it now waits on /api/healthcheck like its seed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:53:17 +02:00
admin 35dd5cf03c uptime-kuma: 2.4.0 -> 2.5.5, its first ladder step, both venues proven (R-462)
gates / gates (push) Successful in 1s
A new front-door fixture: the app's own socket.io messages over plain HTTP polling (setup, login,
addStatusPage), read back through its public /api/status-page/<slug>, with an absent-slug control.
Bench 9401 (harness v4, swap 0): proven, 10-min watch 0 kills (anon peak 43.3 %); the abort starts
and serves. Box 9202: done in 33.9 s through the setup gate, the status page read back.
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:51:17 +02:00
admin e1f0179d7a crafty-controller: 4.10.7 -> 4.11.0, its first ladder step, both venues proven (R-462)
gates / gates (push) Successful in 2s
A new front-door fixture: crafty's own API v2 (login as admin, a role created and read back, a
wrong-token control), reached over its own HTTPS port on the bench as traefik reaches it on a box.
Bench 9401 (harness v4, swap 0): proven, 10-min watch 0 kills (anon peak 2.7 % of 2048M); the abort
starts and serves. Box 9202: done in 51.3 s, the role read back. Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:41:02 +02:00
admin 4ad32aa768 wger: 2.6 -> 2.7, its first ladder step, both venues proven (R-462, R-738)
gates / gates (push) Successful in 2s
A new front-door fixture: the web login, then a weight entry through the app's own API, read back
with a no-session and a never-entered-weight control. Both venues ran on the R-738 template (7a4ff48,
migrations at start): bench 9401 proven, its migrations ran, 10-min watch 0 kills (anon peak 49.8 %);
box 9202 done in 46.2 s, the migrations ran, the entry read back. The first box walk WITHOUT the fix
failed (login 500, 12 migrations unapplied) and wrote nothing. Putting 2.6 back after the migration
loses the entries (recorded, as always; the box's undo restores the pre-update copy instead).
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:25:51 +02:00
admin 5b1972b7f9 harness: five new front-door fixtures, gitea's installer, HTTPS backends, per-file change names (R-462, R-624, R-735)
gates / gates (push) Successful in 2s
- fixtures (upgrade_fixtures_box.py): calibre-web (Upload form -> OPDS readback + the served EPUB),
  wger (web login -> weight entry API), crafty-controller (API v2 roles), uptime-kuma (socket.io
  polling: setup, login, addStatusPage -> public /api/status-page/<slug>); gitea posts its own
  first-run installer form (R-624's fixable case) and keeps the admin CLI for an installed one.
  calibre-web and wger run the template's own after_install on the bench, which has none.
- R-735: the bench's `password:N:special` now has the controller's shape (randomWithSpecial);
  test seen failing first (length 32, no special), then 45/45.
- upgrade_boxport / the memory watch: a backend traefik reaches over https (loadbalancer.server.scheme)
  is reached over https on the bench too (crafty-controller).
- upgrade-test: files-before/after-detail.json and `files_changed_detail` NAME the files behind a
  files_may_change mark (R-734's method, now in the harness); test ChangedFiles.
- test_catalog_gates: the gate count was stale (9, the runner has 10) and red on main; now 10.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:16:45 +02:00
admin 53a4a1ddf7 calibre-web: v4.0.6 -> v4.0.8, its first ladder step, both venues proven (R-462)
gates / gates (push) Successful in 1s
A new front-door fixture: a book uploaded through the app's own Upload form and read back through
its OPDS feed (listed, and the served EPUB carries the marker), with a wrong-password and an
absent-title control. Bench 9401 (harness v4, swap 0): proven, 10-min watch 0 kills (anon peak
19.3 %); the harness saw the household books folder's tree hash change, so the step carries
files_may_change (a night press needs a fresh whole copy). Box 9202: done in 64.6 s, the book read
back, and not one file in the books folder changed (per-file hashes before/after).
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:15:49 +02:00
admin 7a4ff48590 wger: run the database migrations at start (R-738)
gates / gates (push) Successful in 2s
wger's image runs `manage.py migrate` only when DJANGO_PERFORM_MIGRATIONS=True (entrypoint.sh).
Without it, 2.6 -> 2.7 through the guarded Update on 9202 ended `done` and left 12 migrations
unapplied: the web login answered 500 (no such column: core_userprofile.time_zone). With the switch,
the same step on 9202 ran the migrations and the seeded weight entry read back. No image moves here;
no box reporting to the hub runs wger.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/box/wger/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:05:48 +02:00
admin 15f4d5969a home-assistant: 2026.9.3 -> 2026.9.4, within-major step, both venues proven (R-462)
gates / gates (push) Successful in 2s
Bench 9401 (harness v4, swap 0): the onboarding owner seeded and logged in before and after,
10-min memory watch 0 kills (anon peak 32.0 %); the abort starts and serves. Box 9202: the guarded
Update done in 113.8 s through the setup gate, the owner logs in. The old head was a backfilled
harness-v1 entry; this step carries the memory watch. Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 18:05:31 +02:00
admin 9a78e3b757 ghost: 6.65.0-alpine -> 6.67.0-alpine, within-major step, both venues proven (R-462)
gates / gates (push) Successful in 2s
Bench 9401 (harness v4, swap 0): the site set up through /ghost/api/admin/authentication/setup/,
its title read back before and after, 10-min memory watch 0 kills (anon peak 26.7 %); the abort
starts and serves. Box 9202: the guarded Update done in 107.7 s through the setup gate, read back.
Written by upgrade-test.py --write-ladder.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 17:46:01 +02:00
admin 3e4aa7707e emby: 4.11.0.3 -> 4.11.0.4, within-major step, both venues proven (R-462)
gates / gates (push) Successful in 2s
Bench 9401 (harness v4, swap 0): the startup-wizard user seeded and read back before and after,
10-min memory watch 0 kills (anon peak 4.7 %); the abort starts and serves. Box 9202: the guarded
Update done in 23.6 s through the setup gate, read back. Written by upgrade-test.py --write-ladder;
the superseded step keeps its definition in steps/.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 17:45:40 +02:00
admin d7ba60c409 gitea: 1.27.0 -> 1.27.3, its first ladder step, both venues proven (R-462, R-624)
gates / gates (push) Successful in 2s
Bench 9401 (harness v4, swap 0): the first-run installer form posted, a repo seeded and read back
before and after, 10-min memory watch 0 kills (anon peak 26.6 %, cgroup 52.9 %). Box 9202: the
guarded Update done in 21.6 s through the setup gate, the repo read back. Written by
upgrade-test.py --write-ladder. 28.0.0 is a major (2026-09-02 ruling) and is not touched.

Evidence: felhom.eu/documentation/audits/more-night-apps-2026-09-30/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 17:16:56 +02:00
admin 4c552cf56b CHANGELOG + REPORT: immich 768M + v3.2.4 (R-732)
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-30 16:17:17 +02:00