burn-down Part A: 24 rows closed as fixed by later work, 2 duplicates merged, R-376/R-817/R-818 done (335 -> 306); Part A table
gates / gates (push) Failing after 1m16s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 16:53:02 +02:00
parent 58ce696e16
commit f5a0aeb0b8
8 changed files with 714 additions and 38 deletions
@@ -1,5 +1,14 @@
# 08 — The app-down alarm ladder
> **How to read this document.** Where a statement is marked, it is marked like this — the same wording as
> `07-backup-architecture.md:11-17`, carried here on 2026-10-05 (R-376, the three documents written after the
> 2026-08-22 pass):
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **An unmarked statement means "not yet classified", never "observed"** (R-376).
**Written 2026-08-23, with controller v0.222.0 (R-384).**
**The absence is the finding.** Until this file existed, no document owned the question *"when does a
@@ -1,5 +1,14 @@
# 09 — How an app update works, and what it is becoming
> **How to read this document.** Where a statement is marked, it is marked like this — the same wording as
> `07-backup-architecture.md:11-17`, carried here on 2026-10-05 (R-376, the three documents written after the
> 2026-08-22 pass):
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **An unmarked statement means "not yet classified", never "observed"** (R-376).
> **LIVING DOCUMENT. Every slice of the update arc updates this file in the same session.**
> Opened 2026-09-02 with slices 1 and 2. Its absence was **R-438**: the update mechanism was chosen
> deliberately and written down nowhere, which is how a deliberate design gets "fixed" by someone who
@@ -620,6 +629,11 @@ R-636's louder repeated alarm.
controller images are deleted by the same in-use rule as decision 53 — *operator ruling 2026-10-01 (R-745, option 3A).*
**Why:** about 50 old controller versions sat on each demo box (R-745); a release is ~400 MB unpacked and several ship
a day. Registry tags are never deleted by this.
*Clarified 2026-10-05 (R-817), the ruling unchanged:* a swap records the image RUNNING when it starts
(`felhom-agent internal/localapi/controllerswap.go:236-240`, `st.Previous`) and a failed swap writes exactly that
image back (`:289`). After a good swap that image is "the one before" the running one — so „the one before it" here
and R-745's „rolls back to the RUNNING image" name the same image, seen before and after the swap. The controller
never hands the agent an older roll-back target.
### 2026-10-01 — decided by CC unattended, operator may reverse
@@ -1,5 +1,14 @@
# 11 — Operating-system updates: the host, the guest and the Docker engine
> **How to read this document.** Where a statement is marked, it is marked like this — the same wording as
> `07-backup-architecture.md:11-17`, carried here on 2026-10-05 (R-376, the three documents written after the
> 2026-08-22 pass):
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **An unmarked statement means "not yet classified", never "observed"** (R-376).
> | | |
> |---|---|
> | **Status** | **NOT RATIFIED — a PROPOSAL with operator rulings, corrected by the 2026-10-04 spike (§7.1, C1–C12); §8 step 2 BUILT 2026-10-04 (§8.1).** Ratification is Viktor's review, not an editor's. |
@@ -0,0 +1,317 @@
{"id": "R-10", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/appbackup/dbdump.go:364 `if err := tmpFile.Sync(); err != nil {` then :390 `if err := os.Rename(tmpPath, finalPath); err != nil {` with no directory Sync after; the twin at controller/internal/backup/backup.go:948 `_ = dir.Sync()` does sync the dir. Origin: audits/CAMPAIGN-6E-2026-07-15.md:129.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/appbackup/dbdump.go"], "change": "After the os.Rename in DumpOne, open filepath.Dir(finalPath) and call a best-effort dir.Sync() (log at DEBUG on error), mirroring atomicPromoteTar in backup.go:948.", "test": "Unit test that DumpOne still produces the final file and leaves no .tmp; fsync itself is not observable in a unit test, so add a small syncDir seam and assert it is called with the dump directory (red-proof by removing the call).", "minutes": 30}, "not_worth": null, "minutes_spent": 4}
{"id": "R-25", "sev": "P4", "category": "Storage & devices", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/web/storage_handlers.go:153 `uuid := resolveEnrollUUID(ctx, agent, device)` still resolves by device PATH after format, then AssignDisk(uuid) at the next step; FormatResult (controller/internal/agentapi/client.go:384-395) carries DurableID only for the confirmation path, not the new fs UUID. Binding resolve+assign to the format's durable-id needs the agent to return the new fs identity (two repos).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-76", "sev": "P4", "category": "Apps & catalog", "group": "UNCHECKED", "evidence": "Behaviour is FileBrowser-image runtime behaviour (mode/setgid of UI-created folders), only observable on a live box. The image has changed since the finding: controller/internal/infra/infra.go:27 `FileBrowserImage = \"gtstef/filebrowser:1.5.6-stable\"` (finding was on 1.3.3). The comment at infra.go:207-208 still asserts `umask 002 so folders the customer creates here come out group-writable (2775 with the parent's setgid)`, which the row says was false on 1.3.3 — needs a re-measure on 1.5.6 before deciding. Note: the register row itself is truncated mid-sentence (\"does not say t\").", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-89", "sev": "P4", "category": "Business & legal", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No retention policy object in hub: `grep -rln -i 'retentionpolicy|retention_policy' felhom.eu/hub` returns nothing (felhom.eu@53d8131b). Commercial per-customer policy = money/product decision + new reconciler.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-91", "sev": "P4", "category": "Backup & restore", "group": "UNCHECKED", "evidence": "Whether /srv/pbs-felhom still exists on ep0 is live-only (ep0 is protected; not touched). Source-side: CONTEXT.md:3656 still reads \"`/srv/pbs-felhom` is 13 G of dead weight on `/` awaiting R-91's go-ahead\". Extra fact found: documentation/runbooks/offsite-endpoint.md:24 still says the datastore `felhom-offsite` is at `/srv/pbs-felhom` and :119 `proxmox-backup-manager datastore create felhom-offsite /srv/pbs-felhom`, contradicting RUNBOOK-ep0-datastore-volume-2026-07-27.md:8 (moved to /mnt/pbs-datastore). The row's CONTEXT.md:1018 citation is stale (now :3656).", "dup_of": null, "unique_facts": "offsite-endpoint.md:24 and :119 still name /srv/pbs-felhom as the live datastore path (stale since the 2026-07-27 move to /mnt/pbs-datastore) — a doc fix independent of the deletion; CONTEXT citation moved from :1018 to :3656.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-92", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu@53d8131b hub/internal/web/pbsdr_box.go:57 and :64 `view.UsedStr = fmtBytesGB(snap.UsedBytes)`; hub/internal/web/offsite_box.go:54 `return fmt.Sprintf(\"%.1f GB\", float64(b)/float64(int64(1)<<30))` — still 0.1 GB granular. Note the row's own trigger (\"when retention becomes customer-visible\") has not fired.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/web/pbsdr_box.go", "hub/internal/web/offsite_box.go", "hub/internal/web/templates/offsite.html"], "change": "Add an exact-bytes value to the PBS DR view (e.g. UsedBytesExact rendered as a title= tooltip or a MB-precision string below 10 GB) without changing fmtBytesGB for other callers.", "test": "Table test on the view builder: two snapshots 50 MB apart render different exact strings; render test of offsite.html shows the exact value.", "minutes": 30}, "not_worth": null, "minutes_spent": 4}
{"id": "R-93", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Premise gone per the row itself (R-461, CLOSED-ITEMS.md:636): drill-r50 VM no longer exists on either demo box. target-selection.md:111 keeps the fence with the note \"the VM does not exist anywhere, so the fence currently protects nothing\". Only remaining references are comments/tests (hub/internal/monitor/deadline_anchor_test.go:16, deadline_tiers.go:59).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A row about choosing between two fixtures, neither of which exists any more.", "cost": "Building a new synthetic drift fixture is a design task (M), not a fix of this row.", "if_never": "Nothing breaks; there is no drift fixture either way. If one is wanted, it is a new row.", "pick": "close-as-accepted (operator word needed: close, or reopen as 'build a drift fixture'); also drop the dead drill-r50 fence in target-selection.md:111 at close"}, "minutes_spent": 4}
{"id": "R-99", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No phantom-snapshot cleanup in felhom.eu/hub or felhom-agent (grep -i phantom finds only agent runner/test detection code; no removal path). Deletion on a customer datastore is a separate operator ruling per the row — customer data.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-104", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/backup/offbox.go:193-222 ClassifyOffsiteFailure has cases NoUnits/NoRepo/Transport and `default: return OffsiteFailUnknown` — no lock case, although offbox.go:826 already defines `var offboxLockRe = regexp.MustCompile(`repository is already locked`)`. The self-heal half is built (offbox.go:839-858 unlock --remove-all + retry once), as the row's 2026-08-22 note says.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/backup/offbox.go", "controller/internal/i18n/locales/hu.json", "controller/internal/i18n/locales/en.json", "controller/internal/backup/*_test.go"], "change": "Add an OffsiteFailLocked class matched by offboxLockRe in ClassifyOffsiteFailure (before transport) and a cause line in OffsiteFailureMessage telling the operator the repository is locked by an interrupted run and how it clears.", "test": "Table test: a restic 'repository is already locked' error classifies as Locked (red-proof: fails today as Unknown); i18n parity gate for the new key.", "minutes": 50}, "not_worth": null, "minutes_spent": 5}
{"id": "R-124", "sev": "P4", "category": "Backup & restore", "group": "NOT-WORTH-IT", "evidence": "Still true: felhom-agent@e06ed97 internal/hub/dr_recipe.go:61 `const PBSRootNamespace = \"root\"` and :276 `c.Namespace = PBSRootNamespace`; agent internal/pbs/report.go:24 `ns = \"root\"`. The comment at dr_recipe.go:57-58 already documents that PBS spells it \"\".", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The disaster-recovery recipe writes the PBS root namespace as the word 'root', but PBS itself uses an empty name, so a pasted '--ns root' fails.", "cost": "Changing it alters a wire field read by the hub (cross-repo wire contract + recipe producers), for a case no customer has: every box writes a per-customer namespace.", "if_never": "An operator restoring a box with NO namespace line would get one failed command and have to drop --ns; no data risk. The constant's comment already warns.", "pick": "close-as-accepted"}, "minutes_spent": 4}
{"id": "R-129", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "Docs still say no key: felhom.eu@53d8131b documentation/operations/nodes.md:110 `### Access — there is no baked SSH key` and :112 \"no operator public key is on this box\"; target-selection.md:111 still flags R-129 unresolved; MEMORY.md:34 says `ssh demo-hp`, NO KEY→G1. Whether the key works today is a live fact (not checked — no ssh in this pass).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["documentation/operations/nodes.md", "documentation/runbooks/target-selection.md"], "change": "After one read-only `ssh -o BatchMode=yes demo-hp true` (and reading root's authorized_keys comment to name the key), rewrite nodes.md 'Access' section to the measured truth and drop the R-129 caveat in target-selection.md:111-112 (also update the memory index line).", "test": "Positive control: the BatchMode ssh succeeds/fails as the doc now states; repo_gates.py doc gates pass.", "minutes": 30}, "not_worth": null, "minutes_spent": 4}
{"id": "R-134", "sev": "P4", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu@53d8131b hub/internal/cloudflare/unblock.go:117 `for _, name := range []string{domain, parentDomain(domain)} {` and :136-141 parentDomain strips exactly one label (`strings.SplitN(domain, \".\", 2)`); controller strips progressively (controller/internal/cloudflare/zone.go per row).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/cloudflare/unblock.go", "hub/internal/cloudflare/unblock_test.go (new)"], "change": "Extract a pure zoneCandidates(domain) []string that yields the name and every parent down to two labels, and loop resolveZone over it (same order: most specific first).", "test": "Table test on zoneCandidates: 'a.b.felhom.eu' yields [a.b.felhom.eu b.felhom.eu felhom.eu] (red-proof: one-label version yields only two); no HTTP needed because apiBase is a const.", "minutes": 40}, "not_worth": null, "minutes_spent": 4}
{"id": "R-161", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Automatic half exists: app-catalog-felhom.eu@917a779 .gitea/workflows/gates.yml:40 `run: cd ws/app-catalog-felhom.eu && python3 scripts/catalog_gates.py --fast`; .githooks/pre-push:84 runs the same. Only the runtime volume-persistence gate stays a manual periodic run, by operator ruling 2026-08-02.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The runtime check that app data lands on a volume is run by hand, not on every push.", "cost": "Automating it means CI pulling and starting ~53+ app images per push — slow, and the row itself says such CI gets disabled.", "if_never": "A template that writes data outside its volume can ship until the next periodic run catches it; the static gates and pre-push still run.", "pick": "close-as-accepted (residual is a deliberate ruling; owner operator)"}, "minutes_spent": 3}
{"id": "R-162", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Gate is app-catalog-felhom.eu scripts/check-volume-persistence.py (`docker diff` at :41, :72); behaviour on a non-overlay driver is not reachable from source and the row says it fails closed. Status WATCHING, no defect.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "If Docker ever ran on a storage driver where `docker diff` does not work, the persistence gate would refuse to report and blame the prober instead of the driver.", "cost": "A driver probe + reworded message in the catalog script, ~1 h, for a driver nobody runs.", "if_never": "Nothing, unless a non-overlay driver ships; even then the gate fails closed (no false green).", "pick": "close-as-accepted"}, "minutes_spent": 3}
{"id": "R-164", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Predicate still absent: felhom-controller controller/internal/appbackup/dbdump.go:544 still only WARNs `its accounts table has NO rows`; restore still replays dump + tar (internal/backup/restore_unit.go:114-118 hasReplayableDump). Blocked on a design (live-vs-dump per-table counts).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-169", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Working-style ruling owed by operator; nothing in source to fix. Current nets per row: pre-push hooks + Gitea runner alarm (e.g. app-catalog .gitea/workflows/gates.yml:40).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "CI only reports after a push lands, because every repo pushes straight to main with no pull request.", "cost": "Making CI blocking needs branch protection plus a PR workflow for every change — a slower way of working for a one-operator project.", "if_never": "A `--no-verify` push can land broken code until the operator reads the CI alarm e-mail.", "pick": "close-as-accepted (row itself says decide only if the window ever costs something)"}, "minutes_spent": 2}
{"id": "R-177", "sev": "P4", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller@7690c27 controller/cmd/controller/main.go:1546 `sched.Daily(\"fill-watch\", \"03:30\", func(ctx context.Context) error { return fillWatcher.Check() })`; internal/scheduler/scheduler.go:269 has GetJobs but grep finds no RunNow/Trigger method and no run-job route in internal/web. Needs a new operator-gated trigger endpoint (auth surface) — a new mechanism, solve together with R-279.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-184", "sev": "P4", "category": "Box system & updates", "group": "FIXED-BY-LATER-WORK", "evidence": "Fixed by felhom.eu b55fc17d \"hub v0.102.0 — refuse to vouch a version that cannot be installed (R-273)\" — exactly shape (b), validate at vouch time in the hub. felhom.eu/hub/internal/web/configs.go:1358 `res := s.gitea.PackageDownloadable(ctx, t.pkg, t.version, t.file)` and :1365 `s.logger.Printf(\"[WARN] artifact vouch REFUSED: %s package %s is NOT downloadable (R-287)\", ...)`; tag leg at :1343 TagServesFile; unreachable registry also refuses.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-194", "sev": "P4", "category": "Box system & updates", "group": "NOT-WORTH-IT", "evidence": "PVE behaviour, not our code; row states the self-repair already tolerates it (fires on the next probe after the cache clears). No source change to check.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Proxmox caches permissions, so a removed storage grant can still read as present for seconds to minutes; our self-repair notices only after the cache expires.", "cost": "Adding a second signal (storage content listing) to the agent's grant probe is a new mechanism, needs live measurement on a box.", "if_never": "A lost grant is noticed up to ~16 min late; the repair still happens on its own.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-206", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "homelab-manifests@87dfc29 (/home/kisfenyo/git/homelab-manifests): no daemon.json template in homelab-ansible (grep finds only a comment at roles/node_housekeeping/templates/node-housekeeping.sh.j2:17 and homelab-ansible/CLAUDE.md:54). Part (b) was superseded by fc9fbb8 (\"correct the expired Docker rationale\"): the script now says at :13-20 do NOT add docker calls, the GC policy in daemon.json is the control point. DooPlex work — not unprompted.", "dup_of": null, "unique_facts": "Part (b) (prune in the role) is superseded by fc9fbb8 — the role now deliberately forbids docker calls and names daemon.json GC policy as the control; remaining scope is (a) template daemon.json in Ansible + (c) restart-and-verify.", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-207", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "Fixed by homelab-manifests fc9fbb8 \"node_housekeeping: guard DRY_RUN, correct the expired Docker rationale, pin container log rotation\". /home/kisfenyo/git/homelab-manifests/homelab-ansible/roles/node_housekeeping/templates/node-housekeeping.sh.j2:137 `if [[ \"${DRY_RUN}\" == \"1\" ]]; then` inside write_metrics, :138 logs \"file left untouched\".", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-208", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/Dockerfile:12 `ARG VERSION=dev` and :13 `ARG GIT_COMMIT=unknown` sit above :19 `RUN go mod download || true`; felhom.eu@53d8131b hub/Dockerfile:3 `ARG VERSION=dev`, :4 `ARG BUILD_TIME=unknown` above :9 `RUN go mod download || true`.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller (and the identical one-line move in felhom.eu/hub — two repos, each trivial)", "files": ["controller/Dockerfile", "felhom.eu: hub/Dockerfile"], "change": "Move the ARG VERSION/GIT_COMMIT (controller) and ARG VERSION/BUILD_TIME (hub) declarations down to just above the final `go build` RUN.", "test": "Build twice with different --build-arg VERSION on a clean tree; the second build must show `RUN go mod download` CACHED; `--version` of the built binary still shows the passed version.", "minutes": 30}, "not_worth": null, "minutes_spent": 3}
{"id": "R-209a", "sev": "P4", "category": "Process & tooling", "group": "UNCHECKED", "evidence": "Live-only: whether DooPlex has rebooted and /var/log/felhom-store-postboot-check.log says PASS. Not read (DooPlex is Tier 2, operator ruled no reboot; this pass touches no machine). No source claim to check.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-210", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Operator ruling owed; row records CC's view 'not worth doing for the space' (~27 GB reclaimable vs 199 GB free). Workspace CLAUDE.md also forbids `docker image prune -a` on DooPlex.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "193 old controller/hub images exist only on DooPlex and cannot be re-pulled; the question is whether to delete them.", "cost": "An operator decision plus a careful targeted delete on the production host; returns ~27 GB.", "if_never": "~27 GB stays used on a disk with ~199 GB free; old images remain as clutter (and as the only copies of very old builds).", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-213", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Row is a not-started design (live-vs-backup comparison, then put-back flow), operator-owned; nothing in source to verify against.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-230", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Owed rulings, not code: (a) bulk-correction ruling on MEMORY.md staleness (MEMORY.md index still carries version literals, e.g. 'ctrl 0.224.0', 'hub 0.109.0'); (c) spec-as-failing-test pilot not started. (b) closed. Operator decision required.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-246", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu@53d8131b hub/internal/store/store.go:3248 `func (s *Store) MarkEscrowStale(hostID string) error {` still has no production caller (grep: only definition + comments at offsite.go:208,216); stale_at still read (store.go:3182 clears it). Ruling owed by operator: evidential setter or retire the column (folds R-248).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-256", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/i18n/locales/hu.json:1406 `\"flash.offbox.mgr_unavailable\": \"A mentéskezelő nem elérhető.\",` used at controller/internal/web/offbox_handlers.go:54 and :197; sibling :1407 mgr_unreachable used at offbox_handlers.go:582; en.json:1415-1416 same shape.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/i18n/locales/hu.json", "controller/internal/i18n/locales/en.json"], "change": "Rewrite flash.offbox.mgr_unavailable / mgr_unreachable in both languages to say the backup service is not running yet and give a route (try again in a few minutes; if it persists, contact support).", "test": "i18n parity/accent gates (controller_gates.py) pass; a handler test with nil backupMgr asserts the redirect carries the key (exists or add one). Owner is operator (copy) — needs a nod on the wording.", "minutes": 20}, "not_worth": null, "minutes_spent": 4}
{"id": "R-261", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu@53d8131b hub/internal/store/selfbind.go:111 `func (s *Store) CountSelfBindTokens(customerID string) (int, error) {`; only callers are hub/internal/web/customer_delete_test.go:511 and selfbind_automint_test.go:29 — no production caller.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/store/selfbind.go"], "change": "Reword the doc comment (selfbind.go:106-110) to say it is a test accessor and name the two tests that pin the auto-mint invariant (selfbind_automint_test.go, customer_delete_test.go) — or, if the operator prefers, add one post-mint production check that logs [WARN] when count != 1.", "test": "Comment-only option: existing tests stay green; production-check option: unit test that a pre-seeded extra token produces the WARN (red-proof).", "minutes": 20}, "not_worth": null, "minutes_spent": 3}
{"id": "R-263", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/settings/settings.go:1655 `// from every other. This is the ONLY writer of StoragePath.BackupTarget — registration must never set` while :1699 `s.StoragePaths[i].BackupTarget = false` (ClearBackupTarget) also writes it; :1684 is SetBackupTarget's write.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/settings/settings.go", "controller/internal/settings/backup_target_role_test.go"], "change": "Change the comment to 'the only writer that GRANTS the role' and add a source-scanning test that finds every `.BackupTarget =` assignment in non-test settings code and fails if any other than SetBackupTarget can assign a non-false value.", "test": "The new test passes today; red-proof by planting a temporary `BackupTarget = true` in another function and seeing it fail.", "minutes": 45}, "not_worth": null, "minutes_spent": 3}
{"id": "R-264", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu@53d8131b scripts/wire_contract_gate.py still allowlists the six with _R264: :242 selfupdate_pending, :246 selfupdate_pending_version, :255 restore_tests.mount_parity, :258 restore_tests.mount_inventory, :281 backup.last_db_dump, :282 backup.last_integrity_check. Each reader is a design per the row.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-266", "sev": "P4", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/report/builder.go:94 `{Mount: \"/\", Label: \"SSD\", TotalGB: sysInfo.DiskTotalGB, UsedGB: sysInfo.DiskUsedGB, Percent: sysInfo.DiskPercent},` — no disk_known on the storage entry; hub has no disk_known (grep empty). Two-repo wire change gated by wire_contract_gate.py.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-279", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No operator/hub path to start an off-site run: grep for offbox run triggers in felhom.eu/hub/internal finds nothing; the only run entry is the customer dashboard handler (felhom-controller controller/internal/web/offbox_handlers.go:270 `if !s.backupMgr.OffboxRunnable() {`). Needs a new operator-authenticated trigger — sibling of R-177, not a duplicate (different job).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-284", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "Not a defect: felhom-controller@7690c27 controller/internal/web/templates/deploy.html:622 `<div id=\"storage-space-warn\" class=\"form-hint\" style=\"color:var(--warn);display:none\">` (hidden by default) and :808 `warn.style.display = freePct < 20 ? 'block' : 'none';` — correct direction. `git log -S \"freePct < 20\"` shows it unchanged since 69698a8 (v0.10.0), and deploy.html at c732fe1 (main as of 2026-08-09) already had display:none at :589. The 2026-08-09 report read raw HTML without running scripts.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A reported 'almost full' warning on an empty disk; the code shows the warning only below 20% free and hides it by default, so the report was a reading of unrendered HTML.", "cost": "Nothing to fix; a JS render test would need a browser harness the project does not have.", "if_never": "Nothing — the warning never showed on a 93%-free disk.", "pick": "close-as-accepted (close as not-a-defect)"}, "minutes_spent": 5}
{"id": "R-285", "sev": "P4", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No maintenance/expected-downtime concept in hub: `grep -rln -i 'maintenance|expected_downtime|quiet_until|snooze' felhom.eu/hub/internal` returns nothing. New mechanism (M).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-286", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "Lesson (a) not written anywhere: grep -i 'different channel|same channel|independent channel' over documentation/runbooks/workspace-CLAUDE.md, felhom.eu/skills/*, .claude/rules/* returns nothing; felhom.eu/skills/felhom-evidence/SKILL.md:52 has the positive-control rule ('Plant the thing, find it...') but not the different-channel requirement. Part (b): RUNBOOK-hub-db-offsite-backup.md:101 copies a snapshot ($SNAP) not bare hub.db, so no bare-`cat hub.db` runbook found in runbooks/.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["skills/felhom-evidence/SKILL.md (or documentation/runbooks/workspace-CLAUDE.md standing rule 3)"], "change": "Add one paragraph: a positive control must come from a different channel than the measurement (different query path, snapshot, API or clock); give the 2026-08-09 stale-snapshot case as the example. Put it in ONE home (pointer elsewhere).", "test": "python3 scripts/check_skills.py and repo_gates.py pass; re-read the skill to confirm it loads.", "minutes": 25}, "not_worth": null, "minutes_spent": 6}
{"id": "R-287", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "Deleter established 2026-08-10 (R-267 newest-10 prune, recorded in the row itself); CI fixed by felhom-agent 53d047a \"Two guards, one number: bound the published check to the retention it must live with\" (R-291). felhom-agent@e06ed97 scripts/check-published-versions.py:101 `RETENTION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"retention-policy.json\")`, :213 `keep = retention_kept()`; scripts/retention-policy.json:37 `\"generic_versions_kept\": 10,`. Follow-up row R-291 is open (OPEN-ITEMS.md:439).", "dup_of": null, "unique_facts": "scripts/retention-policy.json _comment lines ~15-17 still say no register row records a package prune and 'Container packages currently hold 19 each' — both withdrawn by R-287 (prune is in R-267; 19 was an unpaginated count, real 270/169). Move this stale-comment fix into R-291.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-288", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu@53d8131b documentation/architecture/00-capability-map.md is now 210 125 bytes / 30 937 words / 253 lines (`wc`), larger than the 134 642 bytes measured in the row; :38 still reads `*Verified 2026-07-16 against evidence corpus @ felhom.eu tip `4b18cc5``. Restructure is an M doc surgery, operator-owned.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-289", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "R-182 was closed by felhom.eu ef6ac6fe (2026-08-22, register compression): documentation/backlog/CLOSED-ITEMS.md:474 `| **R-182** | ... | **CLOSED — SHIPPED** (controller v0.194.0 + hub v0.90.0/.1, 2026-08-03) |`. The residue (digest never seen delivering) was since observed: documentation/audits/DRILL-chaos-night-2026-09-17.md:181 `backup_run_failures` „1 of 12 apps failed to back up in this nightly run: nextcloud\" listed as an alarm that fired and was true.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-290", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Gate exists (felhom.eu scripts/check_stands.py) but the map itself still carries the claims: documentation/architecture/00-capability-map.md has 95 'PROVEN-LIVE' occurrences (grep -c); demoting 12 rows or writing walk documents is M and blocked on R-288 per the row.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-291", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-agent/scripts/retention-policy.json still says the 10 is 'NOT a ruling anyone has been able to locate' and recorded_by: \"CC, from the registry's observed state; NOT from a located operator ruling\" — but R-287 (OPEN-ITEMS.md:424) records it IS the operator's newest-10 rule executed under R-267. The file also lists a reader 'documentation/runbooks/registry-retention.md (felhom.eu)' that does not exist (find under felhom.eu/documentation returns no such file). check-published-versions.py:101-104 reads the number. The deeper min_agent-floor bound still needs hub network (not small, recorded only).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-agent", "files": ["scripts/retention-policy.json"], "change": "Rewrite the _comment/recorded_by to cite the operator's newest-10 rule (R-267/R-287) instead of 'observed, not a ruling', and drop or correct the non-existent registry-retention.md reader. Keep the min_agent-floor note as the recorded better bound; then close R-291.", "test": "python3 scripts/agent_gates.py --fast (check-published-versions reads the file; JSON must still parse and generic_versions_kept stay 10)", "minutes": 20}, "not_worth": null, "minutes_spent": 8}
{"id": "R-292", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "hub/internal/web/templates/configuration.html:55 still reads 'the Gitea sha lookup failed (version missing / Gitea unreachable) or the manually-entered sha is invalid'; configs.go:1375 and :1395 both redirect to flash=artifact_sha_invalid; resolveArtifactSHA returns only (sha, ok bool) so the cause is lost.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/internal/web/configs.go", "hub/internal/web/templates/configuration.html"], "change": "Make resolveArtifactSHA return a reason (not-found / unreachable / bad manual sha) and redirect to three distinct flashes (reuse artifact_unverifiable for unreachable, add artifact_version_missing, keep artifact_sha_invalid for a bad typed sha incl. the wrapper sha at :1395).", "test": "Handler test per cause asserting the redirect flash, with a fake gitea returning 404 / network error and a malformed manual sha; red-proof by running against old code.", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-310", "sev": "P4", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/felhom-host-install.sh:3060 sets GOLDEN_CHECK_WHY=\"it is controller $ver, but the vouched golden is $ART_GOLDEN_VER\" and :3080-3081 die \"...${GOLDEN_CHECK_WHY}.\\n The vouched golden is ${ART_GOLDEN_VER:-<unknown>}.\" — duplicate stands. :984 still reads the vmid confirm from /dev/tty; no runbook (day0-install.md, RUNBOOK-byo/appliance-deployment.md mention --uninstall) names the pty requirement.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh", "documentation/runbooks/day0-install.md"], "change": "Drop the second 'The vouched golden is' sentence when GOLDEN_CHECK_WHY already names it (or drop the version from :3060); add one runbook line: --uninstall needs an interactive terminal; --force does not bypass the typed vmid confirm.", "test": "bash -n on the script + python3 scripts/repo_gates.py --fast (hostinstall gate); grep the die text renders the version once.", "minutes": 20}, "not_worth": null, "minutes_spent": 6}
{"id": "R-315", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/scripts/wire_contract_gate.py:30-48 still documents the test as a repo-wide literal-tag search ('IT PROVES REACHABILITY OF A NAME'); ROOTS at :88 includes the R-311 escrow/retained root. No receiver-type field-by-field comparison exists. Fix requires resolving receiver mirror types — a new mechanism (M).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-325", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/scripts/retrieval_promise_gate.py:54 still has its own literal STEMS = [\"visszaállíthat\", ...]; felhom.eu/scripts/hub_copy_gate.py:193-205 still reads it as a drift check. controller_gates.py:48-53 already imports shared scripts from the felhom.eu sibling, so the pattern exists.", "dup_of": null, "unique_facts": "Two-repo sequencing: hub_copy_gate.py:203-205 returns 'drift' when it cannot find a STEMS list, so the felhom.eu drift check must be removed in step with (or tolerate) the controller change.", "small_fix": {"repo": "felhom-controller", "files": ["controller/scripts/retrieval_promise_gate.py"], "change": "Import RETRIEVAL_STEMS from ../felhom.eu/scripts/customer_copy_vocab.py (same sibling-path pattern as controller_gates.py:48) and delete the STEMS literal; absent sibling = INCONCLUSIVE exit 2. Follow-up (felhom.eu, separate commit): remove hub_copy_gate.py's drift check, which would then fail to find STEMS.", "test": "python3 controller/scripts/controller_gates.py --fast; red-proof: remove a stem from the shared list and see the controller gate's decoy convict.", "minutes": 40}, "not_worth": null, "minutes_spent": 6}
{"id": "R-327", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/documentation/architecture/where-felhom-stands.yaml:126-130 still: id claim.code-naming, title \"The same word is used for two different secrets across three surfaces; the email points at a page a rebuilt machine does not show\", status: partial. Needs the operator's capability-map ruling first (dataset may not be raised on its own).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-331", "sev": "P4", "category": "Storage & devices", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/internal/agentapi/diskverdict.go:34 uncorrectableFailCount = 64; :33 comment still defers 'growth-rate detection once the box keeps history'. No growth-rate rule found. New mechanism (M).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-336", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No source change reduces the ep0 poll rate (pvestatd interval is Proxmox-side, not in our repos). Design question (does the hub need a 15-min fill reading) remains; acceptance needs an ep0 access-log measurement. Scaling item, not small.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-337", "sev": "P4", "category": "Monitoring & notifications", "group": "UNCHECKED", "evidence": "Live-only behaviour (WATCHING). From source: felhom-agent/internal/localapi/server.go:518 serves GET /backup/status and :1258 answers from s.pickLatestBackup (the in-memory store), which suggests collection cadence, but the refresh path after an out-of-schedule run was not established within the time box.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-345", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/hub/Makefile:21 'docker tag $(IMAGE):$(VERSION) $(IMAGE):latest' and :22 'docker push $(IMAGE):latest' still present; only commit touching the Makefile is 77b5a4ce (initial).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/Makefile"], "change": "Delete lines 21-22 (or move them behind an explicitly named opt-in target with a comment). Whether a stale :latest already sits on the registry is a separate live check for a session allowed to query it.", "test": "make -n docker-push | grep -c latest == 0", "minutes": 10}, "not_worth": null, "minutes_spent": 3}
{"id": "R-346", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "grep for ActiveEnterTimestamp|ExecMainStartTimestamp|InactiveExitTimestamp across felhom.eu/scripts, hub, felhom-agent, felhom-controller/controller, homelab-manifests (*.sh/*.go/*.py) returns ZERO hits; the R-341 row (now in CLOSED-ITEMS.md:211) carries the lstart reasoning. The row's only remaining action (check for other systemd-timestamp anchors) is answered: none exist.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A warning that a future reader might anchor an uptime slope on systemd's ActiveEnterTimestamp instead of the process start time.", "cost": "Nothing left to fix; the audit the row asked for finds no such use anywhere.", "if_never": "A future script could make the mistake; there is no current instance.", "pick": "close-as-accepted (audit done, zero instances)"}, "minutes_spent": 4}
{"id": "R-348", "sev": "P4", "category": "Monitoring & notifications", "group": "STILL-TRUE-SMALL", "evidence": "felhom-agent/internal/backup/store.go:28 still reads '// Backups are unaffected — their freshness has a ground truth on the storage (R-84).' The hub-side pin the row asks for ALREADY EXISTS: felhom.eu/hub/internal/monitor/deadline_anchor_test.go:52 TestBackupFreshness_AgentRestartBlindWindow_NoAlarm, :295 TestNewestBackupEvidence_ReachesPastEmptyReports, :366 TestCheckBackupDeadlines_RestartBlindWindow_NoEvent; constant at hub/internal/monitor/deadline.go:36 backupEvidenceLookback.", "dup_of": null, "unique_facts": "Hub pin test already exists (deadline_anchor_test.go:52/:295/:366) — only the agent comment remains.", "small_fix": {"repo": "felhom-agent", "files": ["internal/backup/store.go"], "change": "Reword the comment: the backups list IS lost on restart and refills only when a backup runs; the hub's freshness VERDICT is unaffected because it looks back 7 days (hub monitor/deadline.go backupEvidenceLookback, pinned by deadline_anchor_test.go TestCheckBackupDeadlines_RestartBlindWindow_NoEvent).", "test": "Comment-only; go vet ./internal/backup; the hub tests named above already pin the consequence.", "minutes": 10}, "not_worth": null, "minutes_spent": 6}
{"id": "R-352", "sev": "P4", "category": "Storage & devices", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Placement half is an open operator ruling (SPEC-app-data-placement-2026-08-21.md, 'Viktor rules'); deploy route still has no server-side default: GetDefaultStoragePath has no caller in internal/stacks (grep returns only internal/api/router.go:1137 systemInfo). Point (2) is carried by R-368.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-364", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "No helper exists: ls felhom.eu/scripts shows nothing grep/accent-related, and no script mentions '0x80' or 'negative control'. The proposal is a self-contained tool.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/hu_grep.py (new)", "scripts/test_hu_grep.py (new)"], "change": "A helper that, for a pattern containing a byte >= 0x80, also runs an ASCII anchor (must hit) and a negative control (must miss) and refuses to print a zero unless both behave; documented in the felhom-evidence or ui-hungarian rule as the way to search Hungarian text.", "test": "Unit test with a temp file: accented string present -> count; transformed (e.g. octal-escaped) input -> REFUSED not zero; red-proof by disabling the anchor check.", "minutes": 60}, "not_worth": null, "minutes_spent": 4}
{"id": "R-365", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/i18n/locales/hu.json:368 'A kérésed szerint a korábbi távoli mentéseidet <strong>{{.AbandonDate}}</strong> napján véglegesen töröljük (még ... nap)'; handlers.go:1164-1167 sets AbandonDate/DaysLeft with no overdue branch; backups_remote.html:189 renders it unconditionally.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/handlers.go", "controller/internal/web/templates/backups_remote.html", "controller/internal/i18n/locales/hu.json", "controller/internal/i18n/locales/en.json"], "change": "Set AbandonOverdue when DueAt is past and render a new key ('a törlés esedékes, a következő napi karbantartáskor lefut' / English twin) instead of the future-tense sentence.", "test": "Render test with a clock past DueAt asserting the overdue key and not 'töröljük'; i18n parity + copy gates via controller_gates.py --fast; search with ASCII fragments.", "minutes": 45}, "not_worth": null, "minutes_spent": 5}
{"id": "R-367", "sev": "P4", "category": "Backup & restore", "group": "NOT-WORTH-IT", "evidence": "Prune guard confirmed still present: felhom-controller/controller/internal/backup/backup.go:1438 'continue // GUARD: an undeployed app's last backup is still its restore point'. The stranded file itself is on demo-hp (Tier 0) and was not checked live.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "One old 312 KB paperless database dump on the demo-hp test box sits under the app's old folder name; nothing reads or deletes it.", "cost": "An operator ruling plus a live hand-move or delete on one demo box, and an explanation that the adopted dump would not match the volume copies.", "if_never": "A small file stays on a disposable demo box; no customer is affected and the fix for new dumps already shipped (R-355).", "pick": "close-as-accepted (or delete it the next time the box is reprovisioned)"}, "minutes_spent": 4}
{"id": "R-368", "sev": "P4", "category": "Storage & devices", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/settings/settings.go:572 'IsDefault bool `json:\"is_default,omitempty\"` // new apps use this by default' (line moved from 453); the default is honoured only in templates/deploy.html:614 '{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}'; no internal/stacks caller of GetDefaultStoragePath/IsDefault.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/settings/settings.go"], "change": "Reword the field comment: the deploy FORM pre-selects this path (templates/deploy.html:614); the deploy API applies no default when HDD_PATH is omitted. (The alternative — a server-side default — is a behaviour change and not small.)", "test": "Comment-only; go vet ./internal/settings. Optionally a template render test asserting the default path is 'selected'.", "minutes": 15}, "not_worth": null, "minutes_spent": 5}
{"id": "R-371", "sev": "P4", "category": "Monitoring & notifications", "group": "NOT-WORTH-IT", "evidence": "Controller notifier events (internal/notify/notifier.go pushEventMsg list) include db_dump_completed, crossdrive_completed (:933) but no off-site success event; hub has no offsite *_completed type except offbox_abandon_completed.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The weekly off-site backup sends no 'done' event, while the two local tiers do. Failures and an 8-day staleness deadline are already alarmed.", "cost": "A new event type in the controller plus the hub allowlist and severity mapping (two repos), or a written decision that silence-on-success is intended.", "if_never": "The operator sees off-site success only through its absence of alarms and the backup card, as today.", "pick": "close-as-accepted with one line in 07-backup-architecture saying success is silent by design because failure and staleness are alarmed"}, "minutes_spent": 6}
{"id": "R-372", "sev": "P4", "category": "Hub & operator", "group": "NOT-WORTH-IT", "evidence": "No hub or controller surface for 'never produced a copy' (grep 'never produced|NeverProduced' finds only an unrelated comment at felhom-controller/controller/internal/backup/backup.go:724). The underlying F-6E-1 was judged demo-data churn and the controller warns loudly.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "An optional idea from July: show 'this second-drive copy was never made because its source is missing' separately from 'last copy failed' in the operator screen.", "cost": "A design call on the operator view plus a new state carried controller->hub->UI.", "if_never": "The operator keeps seeing the existing loud warning; no data risk.", "pick": "close-as-accepted"}, "minutes_spent": 5}
{"id": "R-373", "sev": "P4", "category": "Box system & updates", "group": "FIXED-BY-LATER-WORK", "evidence": "Premise (20G/50G two-volume mismatch, 'nothing sets SysDataGrowGB') was retired by agent v0.120.0 one-data-volume work, commit cd6e267 'v0.120.0 — one data volume (R-165...)'. felhom-agent/internal/reconcile/bringup.go:191 '// SysDataGrowGB is a COMPATIBILITY INPUT since agent v0.120.0 (R-165). There is no longer a second' and :437 'growGB := spec.DataVolGrowGB + spec.SysDataGrowGB'; installer passes it (felhom-host-install.sh:3140 '-sysdata-grow \"$SYSDATA_GROW\"') and records the sizing at :2041-2058. Note: cd6e267 predates the row's filing date; the row quoted an older audit.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-374", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "felhom.eu/documentation/audits/CAMPAIGN-12-class-sweep-2026-08-08.md:121-123 still says 'three of the 19 were called borderline and left unfiled' without naming them; the doc's only commit is b7fb2117 and no evidence file in audits/ lists the 19.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A July audit says three borderline cases were left unfiled but never named them.", "cost": "Re-running the whole C1 refusal sweep to re-find 19 cases and guess which three were meant — hours, and the guess cannot be checked.", "if_never": "Nobody can re-judge those three; the refusal class has had later sweeps and gates.", "pick": "close-as-accepted, with one line in the audit saying the three are not recoverable"}, "minutes_spent": 5}
{"id": "R-375", "sev": "P4", "category": "Backup & restore", "group": "UNCHECKED", "evidence": "Requires a read-only check on ep0 (token's datastore audit permission); not verifiable from source and ssh is out of scope for this checker.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-376", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "Legend present ('not yet classified') in 00..06 and 10 of documentation/architecture/, but MISSING in the newer 08-alarm-ladder.md, 09-update-architecture.md and 11-os-updates.md (grep -ci 'not yet classified' = 0 each); 09 has zero [DESIGN]/[FACT] marks.", "dup_of": null, "unique_facts": "Three architecture docs added after the row (08, 09, 11) lack the legend.", "small_fix": {"repo": "felhom.eu", "files": ["documentation/architecture/08-alarm-ladder.md", "documentation/architecture/09-update-architecture.md", "documentation/architecture/11-os-updates.md"], "change": "Carry the same marker legend paragraph into the three documents written after the 2026-08-22 pass; then close the row, since 'mark as sessions touch them' is a standing practice already in the template, not a defect.", "test": "grep -ci 'not yet classified' returns >=1 in every numbered architecture doc; python3 scripts/repo_gates.py --fast.", "minutes": 15}, "not_worth": null, "minutes_spent": 5}
{"id": "R-377", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/CONTEXT.md:1537 '## Standing rulings' runs to EOF: 189,685 bytes, 0 '###' sub-headings, 153 bullets, 39 distinct S- ids; each ruling starts as a bold paragraph e.g. '**S-39 — \"WE DO NOT KNOW\" IS NEVER DRAWN AS \"FINE\"...'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["CONTEXT.md"], "change": "Turn each ruling's opening bold line '**S-NN — TITLE (date ...).**' into a '### S-NN — TITLE (date)' heading, changing no other byte; no compression, no reordering.", "test": "Scripted transform; assert 39 '### S-' headings and that git diff --word-diff touches only those opening lines (body bytes identical); repo_gates --fast.", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-390", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "Commit 2344589a ('... runbook pveam note'); felhom.eu/documentation/runbooks/RUNBOOK-manual-build.md:154 '2. Run **`pveam update` first** — the `virgin` snapshot's template INDEX is stale too, and a stale index fails as a bogus'.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-391", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog-felhom.eu/scripts/catalog_gates.py has no SHARED_ / observations entry (grep 'SHARED_|observations' returns nothing); app-catalog-felhom.eu/CLAUDE.md has no 'observation' mention.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["CLAUDE.md"], "change": "Take the row's second option: state in CLAUDE.md that the catalog REPORT.md carries no observations section by convention (findings go straight to the register), so gate 11 is not needed here. The runner refactor (first option) is the bigger alternative.", "test": "python3 scripts/catalog_gates.py --fast (instructions gate checks CLAUDE.md length) and python3 ../felhom.eu/scripts/check_skills.py unaffected.", "minutes": 15}, "not_worth": null, "minutes_spent": 4}
{"id": "R-392", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "ls felhom.eu/documentation/architecture shows no agent-tooling/workflow document (00-11 are all product; plus _design-review, _hub-review, _recovery-inventory). Writing a new architecture document is more than an hour and needs the operator's view of the split.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-393", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "No decision-log skill exists (felhom.eu/skills has 9 skills, none mention 'decision log'). Since then .claude/rules/unprompted-work.md §2 requires every unattended decision be recorded in CONTEXT.md + the owning architecture doc and put FIRST in the morning note (§4).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A proposed skill plus helper script to log every decision an unattended run makes.", "cost": "A storage convention, a helper, a skill and rotation rules — a small project with its own acceptance tests.", "if_never": "Unattended decisions are still recorded under the unprompted-work rule (CONTEXT.md + morning note); only minor in-run choices go unlogged.", "pick": "close-as-accepted (superseded in practice by unprompted-work.md §2/§4)"}, "minutes_spent": 4}
{"id": "R-394", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "wc -l felhom.eu/skills/felhom-build-deploy/SKILL.md = 186 (was 179 at filing — grew); scripts/check_skills.py:45 GRANDFATHERED still holds the exemption. Trim needs a session that can verify the build/deploy commands it keeps.", "dup_of": null, "unique_facts": "The skill has grown from 179 to 186 lines since filing.", "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-402", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No hub Go/template reads last_integrity_ok/_depth (grep in hub/internal returns nothing); still allowlisted at felhom.eu/scripts/wire_contract_gate.py:163 and :220. Needs the operator's decision on what the screen says.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-416", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "Partly covered: scripts/register_shape_gate.py:120 'RULE 3 — duplicate: {rid} already has a row at line ...' (added in 462ab4a5, R-627) now refuses a duplicate id WITHIN OPEN-ITEMS.md only (REG path at :84 = OPEN-ITEMS.md). CLOSED-ITEMS.md has no within-file duplicate rule; closed_register_gate.py:53 still lists '4. A duplicate id WITHIN one register escapes.'", "dup_of": null, "unique_facts": "OPEN-ITEMS half is already fixed by register_shape_gate RULE 3 (462ab4a5); only CLOSED-ITEMS remains.", "small_fix": {"repo": "felhom.eu", "files": ["scripts/register_shape_gate.py or scripts/closed_register_gate.py", "scripts/test_gate_decoys.py"], "change": "Apply the existing RULE 3 duplicate check to CLOSED-ITEMS.md too (suffixed ids like R-88a/R-88b stay distinct), and update the closed_register_gate.py:53 hole list to point at it.", "test": "Planted-duplicate decoy in a temp CLOSED file must convict; suffixed-id control must pass; run against the live CLOSED-ITEMS.md first to see it is clean.", "minutes": 35}, "not_worth": null, "minutes_spent": 7}
{"id": "R-418", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "It drifted AGAIN: felhom.eu/scripts/repo_gates.py docstring lists 1-14 (+9b) = 15 gates while GATES has 17 — 'script-tests' and 'decoy-coverage' are registered but not listed (python import: len(GATES)=17). No test compares the two.", "dup_of": null, "unique_facts": "Live drift right now: script-tests and decoy-coverage missing from the docstring.", "small_fix": {"repo": "felhom.eu", "files": ["scripts/repo_gates.py", "scripts/test_repo_gates.py"], "change": "Add the two missing gates to the docstring list and a test that parses the docstring's gate labels and asserts they equal [g[0] for g in GATES].", "test": "python3 -m unittest scripts/test_repo_gates.py; red-proof: the test fails on today's docstring before the two lines are added.", "minutes": 30}, "not_worth": null, "minutes_spent": 5}
{"id": "R-420", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "felhom.eu/scripts/repo_gates.py:106 tuple is (label, path, args, fast, exemptible) — still no 'blocking' field, as the row says; no gate there needs one.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The felhom.eu gate runner cannot mark a gate as advisory-only; the controller runner can.", "cost": "Add a field to the runner when a need appears.", "if_never": "Nothing today; no advisory gate is wanted in that repo.", "pick": "close-as-accepted (add it with the first gate that needs it)"}, "minutes_spent": 3}
{"id": "R-421", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Deliberate class row ('stays open as the place the next instance is recorded'); its open instances R-422..R-426 are still open in this batch. Not a fixable item by itself.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-422", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/scripts/reuse_refs_check.py:41 PATH_RE still ends '\\.(?:go|py|html|css|yml|yaml|sh)\\b' — no .md. Widening it needs a false-positive walk across all four repos' REUSE.md/CLAUDE.md citations (the row says that pass is the work).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-423", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/site_gates.py:22-27 hardcoded PAGES list; website/ today holds exactly those 9 files, so nothing is missed today, but a new page is not scanned.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/site_gates.py", "scripts/test_gate_decoys.py", "scripts/decoy_coverage_gate.py"], "change": "Discover website/**/*.html and FAIL on any page not in PAGES (or glob and keep PAGES only as exceptions); flip the decoy test to expect conviction and drop the 'site' EXEMPT entry.", "test": "Decoy: a temp website/decoy-page.html must now convict; repo_gates --fast green on the real tree.", "minutes": 45}, "not_worth": null, "minutes_spent": 4}
{"id": "R-424", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "felhom.eu/scripts/one_register_gate.py:23 '...defect written under `idea` looks exactly like a proposal to this gate.' — hole declared in the gate itself.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The roadmap gate cannot tell a real defect filed as an 'idea' from a genuine idea.", "cost": "No mechanical fix exists; telling a defect from a proposal is human judgement.", "if_never": "A mis-filed defect could hide on the roadmap until a person reads it.", "pick": "close-as-accepted (hole stays declared in the gate's docstring)"}, "minutes_spent": 3}
{"id": "R-425", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/scripts/offbox_rename_gate.py:16-20 FILES = backups.html, offbox_handlers.go, offbox.go only; templates/backups_remote.html exists and is not scanned, and customer copy now lives in internal/i18n/locales/hu.json (20 'NAS' hits) which is not scanned either.", "dup_of": null, "unique_facts": "Since localisation, the customer copy the gate guards moved to locales/hu.json, which FILES does not include — the gate may now be largely hollow, not only narrow.", "small_fix": {"repo": "felhom-controller", "files": ["controller/scripts/offbox_rename_gate.py", "controller/scripts tests (decoy)"], "change": "Scan by pattern (templates/backups*.html, *offbox*.go) plus internal/i18n/locales/hu.json, or assert FILES against a discovered set so an unclassified file fails; drop its decoy-coverage exemption.", "test": "Decoy: 'NAS-mentés' in a temp backups_offbox_extra.html and in a hu.json value must convict; real tree passes (check the 20 existing NAS strings are allowed forms).", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-426", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/scripts/decoy_coverage_gate.py EXEMPT now has 19 entries (loaded via python): hub-copy is gone, felhom-agent 'release-complete' is new; group (d) gates (hostinstall, wire-contract, due-checks, published, image-resolvable, volume-persistence) all still exempt.", "dup_of": null, "unique_facts": "Count is 19 now, not 20: hub-copy left the list; felhom-agent release-complete joined it.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-427", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "Commit 71b8c8c6 (Backlog triage Part B: '... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS'); felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word (CLOSED, SHIPPED,'. Of the 12 named rows, R-385/387/341/378/405/88a/88b/123 are now only in CLOSED-ITEMS.md; R-190 and R-352 remain open (partly-closed, as the row predicted).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-437", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu 71b8c8c6 'Backlog triage Part B: 125 finished rows + 20 id-less rows moved to CLOSED-ITEMS ... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS (decoys, seen red) ... register 444 -> 325'. felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word'. Gate run today: 'closed-register gate OK — no open work filed as closed, no id in both registers.' (456 closed / 336 open, 0 convicted).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-445", "sev": "P4", "category": "Hub & operator", "group": "NOT-WORTH-IT", "evidence": "Still true in mechanism: hub/internal/web/apps.go:102-106 computes SuggestedLimit from fleet P95 with no deployment-count/live check. But hub/internal/web/apps.go:67 'since := parsePeriod(period, 7*24*time.Hour)' — the detail page defaults to a 7-day window, so a throwaway's samples leave the default view within a week on their own; the 2026-09-01 sample is long outside it.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The hub's per-app memory suggestion can be built from samples of an app that no longer runs anywhere (e.g. a 15-minute test install).", "cost": "An operator ruling plus a hub change (age-out or exclude zero-deployment apps) and a test, ~1-2 h.", "if_never": "An operator glancing at the app page within 7 days of a throwaway install could see a suggestion based on it; after 7 days the default view drops it. Nothing customer-facing.", "pick": "close-as-accepted"}, "minutes_spent": 5}
{"id": "R-451", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/internal/report/types.go ContainerDetailReport still carries only Name/State/CPUPercent/MemoryMB (no image field). Ruled (09 §3 decision 18), build deferred until fleet grows; needs controller payload + hub denormalisation + fleet page across two repos.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-454", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "The five files named are now clean (gofmt -l controller/internal/web/ prints nothing), but `gofmt -l controller` in felhom-controller lists 12 OTHER files today: cmd/controller/main.go, internal/agentapi/diskverdict.go, internal/api/update_reason_test.go, internal/appbackup/namespace_root_test.go, internal/appbackup/r381_undo_naming_test.go, internal/backup/r669_applied_meta_test.go, internal/family/family.go, internal/infra/infra.go, internal/notify/r636_oom_storm_test.go, internal/quiesce/tiers_test.go, internal/stacks/delete.go, internal/stacks/life_records.go. `grep -rn gofmt --include=*.py` in felhom-controller: no hit — controller/scripts/controller_gates.py still has no formatting gate. The row's 'the count can only grow' is proven.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/scripts/controller_gates.py", "the 12 files listed in evidence"], "change": "Add a gofmt -l gate (fails on any listed file, INCONCLUSIVE if gofmt missing) to controller_gates.py, see it red on today's tree, then one gofmt -w formatting commit for the 12 files.", "test": "Run controller_gates.py before (red, lists 12) and after (green); go build ./... and go vet unchanged.", "minutes": 30}, "not_worth": null, "minutes_spent": 5}
{"id": "R-457", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "No later commit references R-457 beyond the filing release (felhom-controller 38d28b5 v0.234.0 / 998aa31 REPORT). No faked-clock CI run exists (grep for faketime/FAKE_NOW in felhom-controller: no hit). The six candidate files are named but unread.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["internal/backup/offbox_test.go", "internal/web/handler_export_upload_test.go", "internal/web/r103_tier2_action_test.go", "internal/web/dashboard_backup_card_test.go", "internal/web/async_restore_test.go", "internal/stacks/installed_test.go"], "change": "Read the six candidates; for each date literal that feeds an assertion evaluated against time.Now(), derive it from now (as the R-457 fix did). The faked-future-date CI instrument is a separate, larger idea and should be split out or dropped.", "test": "Run the touched packages' unit tests that do not reach Docker (or rely on CI); record per file 'literal feeds real-clock assertion: yes/no'.", "minutes": 60}, "not_worth": null, "minutes_spent": 4}
{"id": "R-460", "sev": "P4", "category": "App updates", "group": "NOT-WORTH-IT", "evidence": "Fact about the BookStack app (API token mintable only via web UI; secure cookies over plain http give 419), not a code defect; re-measured 2026-09-21 per row. DB half proven by app-catalog-felhom.eu/scripts/upgrade-test.py.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "BookStack's uploaded files cannot be checked automatically after an upgrade; only its database can.", "cost": "An upstream headless token route, or a browser-driven step DooPlex cannot run.", "if_never": "BookStack upgrades stay half-proven automatically; file survival rests on volume persistence and manual checks.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-464", "sev": "P4", "category": "App updates", "group": "FIXED-BY-LATER-WORK", "evidence": "Lesson homed and harness uses the correct probe. app-catalog-felhom.eu b7ef0c4 'upgrade-test.py: record the engine's own view of its datadir'; app-catalog-felhom.eu/scripts/upgrade-test.py:278 '\"mariadb-upgrade --check-if-upgrade-is-needed --user=root \"'. felhom.eu d6837d98 (SPIKE R-459); felhom.eu/documentation/architecture/09-update-architecture.md:1647 '1. **Ask the engine, not the log.** MariaDB's entrypoint prints `MariaDB upgrade not required` on an' (cites R-464).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-488", "sev": "P4", "category": "Process & tooling", "group": "UNCHECKED", "evidence": "The claim is a measured suite runtime (5.5 min); confirming it needs running go test ./internal/backup, which I did not run (read-only; backup tests may reach real docker on DooPlex). No commit after filing (felhom-controller 24d7c54) mentions R-488 or a test-speed change in internal/backup (git log --grep on internal/backup since 2026-09-13: empty), so it is likely still true.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-492", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "cfg.Paths.HDDPath still defined and read: controller/internal/config/config.go:167 'HDDPath string `yaml:\"hdd_path\"`', :453 envStr(\"FELHOM_PATHS_HDD_PATH\", &cfg.Paths.HDDPath); readers internal/report/builder.go:69, internal/monitor/healthcheck.go:79, internal/api/router.go:1135, internal/web/server.go:988, cmd/controller/main.go:358 and :526. Only 2 test references (1 file).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/config/config.go", "controller/internal/report/builder.go", "controller/internal/monitor/healthcheck.go", "controller/internal/api/router.go", "controller/internal/web/server.go", "controller/cmd/controller/main.go (gitignored dir — use git add -f)", "settings.AutoDiscoverStoragePaths signature"], "change": "Remove Paths.HDDPath, its env binding and each reader's dead global branch, keeping the per-app/discovered fallbacks each reader already uses.", "test": "go build ./... proves no reader remains; existing tests of the six readers stay green; a config test that FELHOM_PATHS_HDD_PATH is no longer read.", "minutes": 60}, "not_worth": null, "minutes_spent": 4}
{"id": "R-494", "sev": "P4", "category": "Install & onboarding", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub/internal/cloudflare/ holds only unblock.go; no tunnel/DNS creation code in hub (grep cfd_tunnel: none). Building it is a new Cloudflare-API mechanism on the hub; operator ruled it non-blocking.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-501", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu a4993272 'CLAUDE.md: the CI-check recipe was wrong in two ways, both measured today'. felhom.eu/CLAUDE.md:176 'rows — a run can sit several pages earlier. **Scan every page** and match on `head_sha`; with a'; recipe at CLAUDE.md:166-168 loops every page.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-502", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/iso/test/bootstrap-modes.sh exists; `grep -rn 'bootstrap-modes|bootstrap_modes' scripts/*.py .gitea/workflows` in felhom.eu returns nothing — no gate or CI runs it.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/repo_gates.py", "scripts/iso/test/bootstrap-modes.sh"], "change": "Register bootstrap-modes.sh in repo_gates.py behind a docker-available check that reports INCONCLUSIVE (never pass) when docker/the felhom-iso-assistant image is absent, plus a decoy (a broken banner must turn it red).", "test": "Run repo_gates.py with docker present (green), with the harness sabotaged (red), and with docker hidden from PATH (INCONCLUSIVE).", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-503", "sev": "P4", "category": "Install & onboarding", "group": "NOT-WORTH-IT", "evidence": "Not built (by design); the 2026-07-31 ruling 'a person chooses the disk' stands per the row; no later commit references R-503 other than its filing context.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "An idea, offered and not chosen: the installer would pick the disk itself when there is exactly one.", "cost": "A spike with three measurements and then reversing two operator rulings.", "if_never": "Nothing changes: a person keeps choosing the disk, as ruled.", "pick": "close-as-accepted (as DECLINED by ruling; the three measurements stay in the closed row for any future reversal)"}, "minutes_spent": 2}
{"id": "R-504", "sev": "P4", "category": "Install & onboarding", "group": "UNCHECKED", "evidence": "The claim (iso.felhom.eu/ returns 404) is live-only; I may not curl hosts. felhom.eu/documentation/runbooks/VOLUNTEER-first-hour.md:14 still says '`iso.felhom.eu/` itself still has no index — R-504'. Fix needs a Cloudflare rule the operator owns. Cosmetic; households use felhom.eu/letoltes (website/letoltes.html exists).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-507", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Needs measuring QEMU input-send-event or a VNC client against a live VM on felhom-pve — a live-machine spike, not a source change. No later commit references R-507.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-525", "sev": "P4", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Row itself states it is a new unmeasured mechanism (forwardAuth / Quantum proxy auth) needing a scratch-guest spike.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-526", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub/internal/tenantsync/client.go:110 '// Deprovision DESTROYS the customer's PBS namespace, all its backup groups, and its token — the'; no token-only op exists. Needs a new op on protected ep0 and an operator yes/no.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-527", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "Row's wording is partly WRONG: the flag IS read — controller/internal/stacks/deploy.go:401 'if field.LockedAfterDeploy {', :790-791 and :1350-1374 copy it into appCfg.LockedFields, and deploy.go:710 UpdateStackConfig refuses a locked key. But UpdateStackConfig (deploy.go:681) has NO non-test caller (grep: only its own definition/logs), and deploy.html:508-518 renders every auto field `readonly`, so the effect the row describes (every field read-only after install regardless of the flag) is true.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A catalog flag that marks some settings 'locked after install' changes nothing visible: the page makes every setting read-only anyway, and the edit path that would honour the flag is never called.", "cost": "Either delete the flag from the catalog and controller (catalog-wide edit + parity fixtures), or build an edit-after-install feature (a product change).", "if_never": "Nothing a household meets: all settings stay read-only, which is the safe direction.", "pick": "close-as-accepted (with the corrected facts: flag read into LockedFields, enforced only by uncalled UpdateStackConfig)"}, "minutes_spent": 8}
{"id": "R-532", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "app-catalog-felhom.eu/templates/vaultwarden/docker-compose.yml:30 '- SIGNUPS_ALLOWED=${SIGNUPS_ALLOWED:-false}' (image vaultwarden/server:1.36.0-alpine, :22). The /api/config field is upstream Vaultwarden behaviour; the server refusal (400) is live-only and not re-measured here.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Vaultwarden's web page shows a sign-up form even though sign-ups are off; the server then refuses it.", "cost": "An upstream fix, or a catalog note; nothing Felhom can change in the server's answer.", "if_never": "A stranger who finds the page sees a form that fails; an invited household is not affected.", "pick": "close-as-accepted"}, "minutes_spent": 3}
{"id": "R-541", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Row: needs a new copy/re-key/release mechanism and a design; no later commit references R-541. Far off per re-rank (0.3% full, one pool box).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-544", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/hub/internal/web/hosts.go:917 's.logger.Printf(\"[INFO] host deleted: %s (escrow deleted: %v)\", hostID, deleteEscrow)'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/web/hosts.go", "hub/internal/web/hosts_delete_test.go"], "change": "Change the log line to state the effect, e.g. 'host deleted: %s (escrow custody demoted to retained)' when escrow existed, and drop the boolean name from the text.", "test": "A test in hosts_delete_test.go capturing the logger output on an acknowledged delete asserts 'demoted to retained' and the absence of 'escrow deleted'; red-proof against today's line.", "minutes": 30}, "not_worth": null, "minutes_spent": 3}
{"id": "R-551", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "Row: behaviour proven by five red-proofed ServeHTTP tests; live proof needs a box in the 'paused AND agent-connected' state, which only a fresh install or a local-API token on scratch guest 9202 (a live-machine change) gives. No later commit references R-551.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The escrow 'waiting for the agent' screens are proven by tests but never seen on a real box in that state.", "cost": "Provisioning a local-API token on the scratch guest, or walking it during the next fresh install.", "if_never": "Small risk the live page differs from the tested page; the state lasts ~17 minutes after a bind.", "pick": "close-as-accepted (add 'walk R-546 readiness branches' to the next fresh-install checklist instead)"}, "minutes_spent": 2}
{"id": "R-555", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/wire_contract_gate.py:451 'def receiver_tokens(repo_root):' tokenises whole files: line 482 'toks.update(TOKEN_RE.findall(fh.read()))' — no comment stripping.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/wire_contract_gate.py"], "change": "Strip Go // and /* */ comments and template {{/* */}} before TOKEN_RE in receiver_tokens; add a decoy 'tag named only in a receiver comment must convict'. Newly surfacing tags each become a finding (allowlist with reason or a row).", "test": "Run the gate: decoy red, real tree result reviewed; the 'language' allowlist entry still justified.", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-564", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/scripts/retrieval_promise_gate.py:54 'STEMS = [\"visszaállíthat\", \"visszaszerezhet\", \"visszahozhat\", \"visszanyit\"]' — joined forms only, no split-verb pattern.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/scripts/retrieval_promise_gate.py"], "change": "Add split-form Hungarian patterns (állíthatók? vissza, (hoz|szerez|nyit)\\w* vissza), register the Hungarian occurrences found (the seven already reviewed in English), and add a planted split-verb decoy.", "test": "Gate red on the decoy, green on the tree after registration; search with ASCII fragments plus positive/negative controls per the Hungarian-search rule.", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-567", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "controller/internal/web/templates/layout.html:83 '{{$storageOpen := or (eq .Page \"storage\") (eq .Page \"storage-network\")}}' — storage_init/storage_attach not included; storage_handlers.go:351 'data := s.baseData(tmpl, title)' passes the template name as Page.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/templates/layout.html", "controller/internal/web/testdata/i18n_parity (re-capture storage_init/attach fixtures)"], "change": "Add (eq .Page \"storage_init\") (eq .Page \"storage_attach\") to $storageOpen and mark the Meghajtók link active for them.", "test": "Render test: GET /storage/init and /storage/attach carry 'nav-group is-open' and the active storage link; red before, green after; re-capture parity fixtures.", "minutes": 40}, "not_worth": null, "minutes_spent": 4}
{"id": "R-568", "sev": "P4", "category": "Storage & devices", "group": "STILL-TRUE-SMALL", "evidence": "controller/internal/web/disk_health.go:124-152 diskHealthRows appends rows in resp.Disks order; no sort in the file (grep 'sort.' in disk_health.go: none).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/disk_health.go", "controller/internal/web/disk_health_test.go"], "change": "Sort rows by diskKey(d) (durable id, falling back to name) before returning.", "test": "Unit test feeding the fake agent two disks in both orders and asserting one identical row order; red-proof by removing the sort.", "minutes": 30}, "not_worth": null, "minutes_spent": 3}
{"id": "R-569", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "controller/internal/api/router.go:742 'if strings.Contains(err.Error(), \"protected\") {', also :745, :1018, :1021, :1024 ('not deployed'/'still running'), :1102, :1105, :1108 ('not orphaned').", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/api/router.go", "controller/internal/stacks (sentinel errors)", "router tests"], "change": "Add KindErrorf-style sentinels in internal/stacks (protected, not found, not deployed/still running, not orphaned), a statusFor helper per handler family replacing the three Contains blocks.", "test": "One table test per family passing a REWORDED error message and asserting the same status code; red against today's string matching.", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-570", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Fallback still present: controller/internal/web/handlers.go:1050 'offboxStaleWarningMarker = \"nincs mentésre jelölt alkalmazás\"'; producer internal/backup/offbox.go:1168. Closing depends on a fleet condition (every box one off-site run on >=0.251.0) — a watch, not a fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-571", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "grep ClassifyOffsiteFailure|PageOnly|Inline in felhom.eu/documentation/architecture/07-backup-architecture.md and 02-controller-module-map.md: no hit. Classifier lives at felhom-controller/controller/internal/backup/offbox.go:193 'func ClassifyOffsiteFailure(err error) OffsiteFailureClass {'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["documentation/architecture/07-backup-architecture.md", "documentation/architecture/02-controller-module-map.md"], "change": "Add a short section to 07 listing the six failure classes, what each means for the customer, and that restic/ssh signatures are external; add an alert-placement paragraph (inline under storage bars vs top banner) to 02.", "test": "Doc-only: grep the two docs for ClassifyOffsiteFailure/PageOnly afterwards; the repo's doc gates stay green.", "minutes": 40}, "not_worth": null, "minutes_spent": 3}
{"id": "R-574", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "controller/internal/web/handler_debug.go still carries 40 lines with accented Hungarian string literals (grep -cP count); last touched by 0c702f8 v0.279.0, not converted. Labelling ~40 literals page-copy vs payload, adding en/hu keys and parity fixtures exceeds an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-576", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/scripts/i18n_go_parity.py has no call-site argument-count or '+'-adjacency check (grep verb/argument: only VERB_RE/strip_verbs for text equality, lines 76-78, 196-199). Parsing multi-line Go call arguments reliably from Python with decoys is likely >1 h.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-577", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Waiting on an operator decision (what the share feature promises a stranger); felhom.eu/documentation/architecture/10-localisation.md table row 'the two guest share pages, the catch-all | a stranger / nobody | **no globe** | — (R-577, the operator's)'.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-579", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Gate deliberately deferred until R-554 deletes the first-boot wizard; R-554 is still OPEN (OPEN-ITEMS.md:131). Versionless links remain in controller/internal/setup/templates/setup_*.html:8 '<link rel=\"stylesheet\" href=\"/static/style.css\">' (8 files).", "dup_of": null, "unique_facts": "NEW: controller/internal/web/templates/monitoring.html:255 '<script src=\"/static/chart.min.js\"></script>' also has no ?v= — outside the wizard, so the future gate must cover or allowlist it. Could be folded into R-554's closing work.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-588", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/documentation/runbooks/iso-release-gate.md:319-322 'Result recording' says only 'in the release report' — names no home. documentation/tests/ holds iso-release-1.27.0, 1.27.1, 1.29.0 only; no record dir for 1.28.0 or later ISOs.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["documentation/runbooks/iso-release-gate.md"], "change": "Name the single home documentation/tests/iso-release-<ver>-<date>/ in the Result-recording section, and add a pointer dir/README for 1.28.0 to its audit record (evidence-backup-promise-2026-09-16/phaseD-iso-gate.txt). Optional existence check left out.", "test": "Doc-only; the repo doc gates stay green.", "minutes": 20}, "not_worth": null, "minutes_spent": 4}
{"id": "R-591", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "The copy is deepCopyStack in controller/internal/stacks/manager.go (row says Copy()); it deep-copies AppConfig (:1137-1148), DeployFields (:1162), OptionalConfig (:1174), Integrations (:1186) and has no I18n line (grep I18n in manager.go: none). Meta.I18n defined at internal/stacks/metadata.go:94.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/stacks/manager.go", "a stacks unit test"], "change": "Deep-copy Meta.I18n in deepCopyStack (map plus nested values).", "test": "Test mutates the copy's I18n overlay and asserts the original is unchanged; red without the copy.", "minutes": 30}, "not_worth": null, "minutes_spent": 4}
{"id": "R-594", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog-felhom.eu/scripts/check-copy-i18n.py: grep ALLOWLIST/allowlist — no hit; no way to register a true occurrence.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["scripts/check-copy-i18n.py"], "change": "Add ALLOWLIST_EN of (app, path, reason); registered occurrences pass, unregistered convict, and any entry matching nothing is itself a failure.", "test": "Decoys: a registered occurrence passes, an unregistered one convicts, a stale entry convicts. Mind catalog CI has no PyYAML (degraded mode must still run).", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-599", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/hub/internal/web/hosts.go:898 'http.Error(w, \"Host is ONLINE — deletion is refused (a live agent would receive 401s permanently).\", http.StatusConflict)' — no last-report age or opening time. target-selection.md: no mention of the stale-threshold wait (grep 45 min|stale_threshold: none).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/web/hosts.go", "hub/internal/web/configs.go (config delete 409)", "documentation/runbooks/target-selection.md"], "change": "Make both 409 bodies say when the last report arrived and when deletion opens (last report + configured stale threshold), and name the wait in target-selection.md's drill section.", "test": "Handler test with a host last reported N minutes ago asserts the 409 body carries the minutes and the opening time computed from the configured threshold (not the 30m literal).", "minutes": 60}, "not_worth": null, "minutes_spent": 4}
{"id": "R-602", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu e02bc038 'hub v0.119.0 — ... R-596/R-598 closed' added the finding; felhom.eu/documentation/architecture/10-localisation.md:809-812 'the `felhom_lang` cookie and got the **Hungarian** page for `en`. ... The cookie is the right instrument for the anonymous claim page and the **wrong**' and :509 '`langFor`'s order is fixed: `?lang=` → **the household's setting when a session exists**'. Only the optional pointer from the workspace live-validation rules is absent (grep ?lang=/felhom_lang in CLAUDE.md files and .claude/rules: none) — a 5-minute add if wanted.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-603", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "No gate or helper: grep for html.EscapeString(want|&#39;|R-603 in felhom-controller/controller scripts+internal: none. controller/internal/i18n/locales/en.json has 27 lines containing an apostrophe today, so the trap is live for any Contains assertion on those.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["a test helper in internal/web (e.g. assertPageContains)", "optionally controller/scripts gate"], "change": "Add a test helper that compares against html.EscapeString(want) and use it in the render tests that assert English copy; the bundle gate with a 27-entry allowlist is the larger alternative.", "test": "A test asserting an apostrophe-bearing en.json value through the helper passes; the same via raw strings.Contains fails (red-proof).", "minutes": 45}, "not_worth": null, "minutes_spent": 4}
{"id": "R-605", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog-felhom.eu/scripts/catalog_gates.py:122 'VERDICT = {0: \"OK\", 1: \"FAILED\", 2: \"INCONCLUSIVE\"}' — harness refusal and per-app undetermined both exit 2 and print the same word.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["scripts/catalog_gates.py", "scripts/check-volume-persistence.py", "scripts/check-image-resolvable.py"], "change": "Give harness-level refusal its own exit code (e.g. 3 = REFUSED) in both scripts and map it to a distinct label in catalog_gates.py.", "test": "Decoys each way: a forced canary failure reads REFUSED, a per-app undetermined reads INCONCLUSIVE.", "minutes": 60}, "not_worth": null, "minutes_spent": 3}
{"id": "R-610", "sev": "P4", "category": "App updates", "group": "NOT-WORTH-IT", "evidence": "felhom-controller@7690c27 controller/internal/stacks/update.go:1453 `case UpdatePhaseStarting, UpdatePhaseVerifying:` - starting and verifying share ONE recovery arm, the one measured three times. No fault injector exists (grep for faultinject/failpoint in controller/ returns nothing).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A power cut landing inside the sub-second `starting` phase has never been measured; all three live cuts landed in `verifying`, which runs the same recovery code.", "cost": "An in-process fault injector in the controller (new mechanism) plus a live drill on a scratch guest.", "if_never": "Nothing new is learned: the code path is the same branch already proven three times; the residual risk is a difference between the two phases that the source does not show.", "pick": "close-as-accepted"}, "minutes_spent": 5}
{"id": "R-617", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu@462ab4a5 (2026-09-22) documentation/architecture/09-update-architecture.md:1969 \"`POST /api/v1/repos/migrate` is the route that works (the project's Gitea tokens carry\" - continues at :1970 \"`write:repository` but not `write:user`, so `POST /user/repos` answers 403\"; recipe at :1979. The one-line note the row asked for exists (in the architecture doc rather than operations/). The optional operator-scoped token is a separate wish, not the defect.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-618", "sev": "P4", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Remaining open work = the controller-side idea (let `verifying` accept docker's own `healthy`). grep for docker health status in felhom-controller@7690c27 controller/internal/stacks/update.go returns nothing; no R-618 reference in Go source. It is an undecided design question (operator), not a defect; the three probe fixes (app-catalog@793c4fb) and the gate scripts/check-probe-matches-compose.py are done.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-619", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/api/router.go:395 `meta, appCfg, err := r.stackMgr.GetDeployFields(name)` then :402 `\"metadata\": meta,` - metadata served verbatim, no derivation of Required for password; deploy.go:366 \"// We never silently auto-generate — the user needs to know their password.\" still refuses.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": "controller/internal/api/router.go (getDeployFields), new test in controller/internal/api/", "change": "In getDeployFields, copy meta.DeployFields and set Required=true for every field with Type==\"password\" before writing the response (copy, do not mutate the shared metadata; the web deploy page uses GetDeployFields separately and is untouched).", "test": "Go unit test: a stack whose .felhom.yml has a type: password field with required: false; GET /api/stacks/<n>/deploy-fields must answer required:true for it and leave a secret field's required as declared; red-proof by running it before the change.", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-621", "sev": "P4", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Capture is done: felhom-controller@7690c27 controller/internal/stacks/update.go:1054 `outDir := filepath.Join(dir, \"hold-logs\", ts)`. The open part (show it on the app page's hold panel / logs fallback) is not built: grep for hold-logs/holdLogs in controller templates and handlers returns only update.go:1050-1082 and undo.go:535,550 (writers). Surfacing needs a page change with HU/EN copy and a design for which hold's log to show.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-624", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Row's own latest update: remaining class is vaultwarden (closed sign-up by design) and code-server; the open decision is whether the harness may hold an app's admin secret (operator). Not verifiable further from source; needs a decision, not a fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-644", "sev": "P4", "category": "Apps & catalog", "group": "UNCHECKED", "evidence": "About the live state of gokapi on scratch guest 9202 (crash-loop, deployed:true). Only the box shows it; no ssh allowed.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-652", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog@917a779 templates/romm/.felhom.yml:249 still carries `\"memory_peak_pct\": 80.9` with `\"memory_tight\": true` and no `memory_basis: anon` (contrast paperless-ngx/.felhom.yml:249 `\"memory_basis\": \"anon\"`). Needs a live re-measure of romm plus an undecided cache-thrash rule.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-654", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "app-catalog@917a779 templates/opengist/docker-compose.yml:11 `image: ghcr.io/thomiceli/opengist:1.15`; grep for \"/-/\" in templates/opengist/.felhom.yml returns nothing - app_info does not mention the moved pages.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "opengist 1.15 moved its pages under /-/; an old /login bookmark answers 404. The front page redirects correctly.", "cost": "A copy decision by the operator plus a HU/EN app_info line.", "if_never": "A household with an old deep bookmark sees a 404 once and re-bookmarks from the front page.", "pick": "close-as-accepted"}, "minutes_spent": 3}
{"id": "R-687", "sev": "P4", "category": "App updates", "group": "NOT-WORTH-IT", "evidence": "Gaps (1)-(3) are unit-tested only (as the row says); item (4) proven live 2026-09-30. Cosmetic text still as described: felhom-controller@7690c27 controller/internal/quiesce/quiesce.go:846 `return true, fmt.Sprintf(\"the automatic update leg is running (it starts no step after %s)\", backupwindow.FmtHHMM(mod1440(startMin+stopMin)))` - derived from the window, not a manual leg's own deadline.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Three live proofs a scratch box cannot give (a 3-hour leg, a failing off-site leg, a files_may_change step without a whole copy) plus one log text that names the window's deadline instead of a manually started leg's deadline.", "cost": "Each live gap needs a special venue (off-site target, long leg); the log text is ~30 min but only matters for the manual debug chain.", "if_never": "The unit tests remain the proof; an operator reading the manual-chain log sees a deadline 20 minutes off.", "pick": "close-as-accepted"}, "minutes_spent": 5}
{"id": "R-688", "sev": "P4", "category": "Hub & operator", "group": "NOT-WORTH-IT", "evidence": "felhom.eu hub/internal/web/customer_delete.go:130 \"// R-688 (v0.125.0): NO leg of the cascade calls Cloudflare. The dialog used to promise\" and :133 `cfManual := cloudflareManualRemoval(cfg)` - the dialog lists the manual removal; no Cloudflare leg exists.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Deleting a customer does not remove their Cloudflare tunnel and DNS records; the dialog now says so and lists what to remove by hand.", "cost": "A new Cloudflare leg in the reset cascade (API calls, ids, failure handling, tests) - a new mechanism touching an external service.", "if_never": "The operator removes a tunnel and a few DNS records by hand at each customer delete, guided by the preview.", "pick": "close-as-accepted"}, "minutes_spent": 4}
{"id": "R-691", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Open work = the Tier 2 (second-drive) path of Use/Load is not live-proven; needs a two-drive Tier-0 box (9202 has one drive). Live-only gap, not a source defect; not checkable from source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-693", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "grep for memory_scales_with_limit / scales_with_limit across app-catalog-felhom.eu, felhom-controller/controller and felhom.eu/scripts returns nothing - no basis that tells growth-to-fill from pressure exists. Needs a design (new harness signal).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-705", "sev": "P4", "category": "Process & tooling", "group": "FIXED-BY-LATER-WORK", "evidence": "The remaining half (manual whole-guest backup) EXISTS and predates the row: felhom-controller bbed5af (v0.47.0, 2026-06-12) 'backups page — whole-guest backup visibility + manual trigger'. Live source @7690c27: controller/internal/web/backup_handlers.go:324 `case r.URL.Path == \"/api/guest-backup/trigger\" && r.Method == http.MethodPost:`; :340 `if err := s.backupTrigger.TriggerNow(); err != nil {`; quiesce.go:427 \"manual backup requested — quiescing now\" (bypasses due-ness, all tiers); wired cmd/controller/main.go:2099 and the page button backups.html:229. Agent side: felhom-agent internal/localapi/server.go:514 `mux.HandleFunc(\"POST /backup\", ...)`. The controller half was built v0.279.0 (night-chain, handler_debug.go:79). The row's 'no manual trigger' claim was not true at writing; it is not chained into night-chain, which the row did not require.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-707", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Open work = live proof that the gate OPENS for seerr, outline and rallly (needs a media server / e-mail on a test box). Live-only proof gap; the gating itself is in source (catalog 6faf432 per row).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-718", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller@7690c27 controller/internal/web/templates/app_info.html:112 `<p>{{T \"app_info.close_signup_text\"}}</p>` - the close card has no restart line, while the window card has one only at :132-133 `{{- if .SignupNative}}` / `{{T \"app_info.signup_window_restart\"}}`. en.json:2505 close_signup_text says nothing about a restart.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": "controller/internal/web/templates/app_info.html, controller/internal/i18n/locales/hu.json + en.json, the app-info handler (if SignupNative is not set when CloseSignupOffered)", "change": "Add a key app_info.close_signup_restart (\"The app restarts once for this.\" / HU twin) and render it under the close card when the app has an after_setup env switch (the same SignupNative fact); add the same sentence to the gate-open confirmation where after_setup.env exists.", "test": "Render test per branch: app with after_setup.env shows the line on the close card, app without it does not (red first); run the design-v2 copy/i18n gates.", "minutes": 60}, "not_worth": null, "minutes_spent": 6}
{"id": "R-719", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Built: felhom.eu hub/internal/web/selfbind.go:160 \"// R-719 (v0.126.0): „Új linket kérek\" on an expired or used link.\" Open part is the operator's review of the changed shape and a live mint+send proof (unit-proven only) - an operator decision, not a CC fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-725", "sev": "P4", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu hub/internal/i18n/locales/hu.json:79 `\"bind.invalid.body\": \"A hivatkozás 7 napig érvényes. Ha lejárt, kérj újat az ügyfélszolgálattól, ...\"` still sits above hu.json:87 `\"bind.resend.button\": \"Új linket kérek\"`; en.json:79 'ask support for a new one'. The console 'V' is the ✔ glyph in scripts/iso/felhom-bootstrap.sh:99 `printf ' Felhom — a doboz össze van kötve. ✔\\n\\n'` (golden scripts/iso/test/golden/bound.hu.txt:2). The English JSON to phone apps is left on purpose.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": "hub/internal/i18n/locales/hu.json, hub/internal/i18n/locales/en.json (bind.invalid.body); optionally scripts/iso/felhom-bootstrap.sh:99 + scripts/iso/test/golden/bound.hu.txt", "change": "Reword bind.invalid.body to point at the button below (e.g. 'Ha lejárt, kérj újat lent.' / 'If it has expired, ask for a new one below.'), keeping the operator alternative; optionally drop the ✔ glyph the console font renders as 'V' and update the golden.", "test": "Hub i18n parity/copy tests and a render test of the expired bind page asserting the new sentence and absence of 'ügyfélszolgálattól'; ISO golden test if the glyph is changed. Ships with the next hub release.", "minutes": 30}, "not_worth": null, "minutes_spent": 6}
{"id": "R-731", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "The shape-switch control lives only in audit tools: felhom.eu/documentation/audits/catalog-currency-2026-09-30/00-currency.py, 04-analyse.py; no standing currency script in app-catalog-felhom.eu/scripts or felhom.eu/scripts (ls/grep for currency/shape returns only golden_currency_gate.py, which is unrelated). Making it standing means promoting a registry-reading tool with tests - more than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-734", "sev": "P4", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "grep for '.immich' / hash ignore list in app-catalog-felhom.eu/scripts/*.py and templates/immich/.felhom.yml returns nothing - no exclusion exists. The row says the rule change needs an operator word; calibre-web shows the mark is sometimes right, so the rule needs design.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-739", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog@917a779 templates/wanderer/docker-compose.yml:117 `image: getmeili/meilisearch:v1.36.0`; grep MEILI_UPGRADE_DB in the compose returns nothing. Remaining: the template switch, a fixture (PocketBase create refused) and a measured step on the bench - live work.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-759", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Five checklist rows of wger need live measurement on 9202 (2.5, 3.7, 6.3, 8.2, 9.1); not verifiable from source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-760", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog@917a779 templates/vikunja/docker-compose.yml: service vikunja (image: vikunja/vikunja:2.6.0, line 12) has no `healthcheck:` key and no comment explaining why (whole file read).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": "templates/vikunja/docker-compose.yml", "change": "Read the vikunja 2.6.0 image config for a HEALTHCHECK/shell; if none and the image has no shell, add a comment saying why there is no compose healthcheck (like adventurelog-frontend's R-655 comment); otherwise add a healthcheck of the family the image supports (REUSE.md §2). No image: line moves, so no catalog_since.", "test": "scripts/onboarding_gaps.py row 4.1 no longer lists vikunja (or lists it as explained); catalog_gates.py --fast green; if a healthcheck is added, a deploy on 9202 must read healthy.", "minutes": 45}, "not_worth": null, "minutes_spent": 4}
{"id": "R-761", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog@917a779 templates/paperless-ngx/.felhom.yml:22 `# Logo: {assets.base_url}/assets/{slug}-logo.webp` vs felhom-controller controller/internal/config/config.go:511 `return fmt.Sprintf(\"/static/assets/%s-logo.svg\", slug)` and :516 `-logo.png`.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": "templates/paperless-ngx/.felhom.yml (comment block lines 20-24)", "change": "Change the comment to name `{slug}-logo.svg` (preferred) and `{slug}-logo.png` (fallback), matching config.go AppLogoURL/AppLogoPNGURL; comment-only.", "test": "grep shows no '-logo.webp' in the template comment; catalog_gates.py --fast green (copy-freeze gates ignore comments).", "minutes": 10}, "not_worth": null, "minutes_spent": 3}
{"id": "R-764", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "grep smtp/mail in app-catalog templates/wger/.felhom.yml and docker-compose.yml finds only first_steps text (.felhom.yml:74 'Add meg az email címedet a beállításokban'); no smtp_mapping. A mapping needs a live boot proof with mail off (REUSE.md §2) - more than an hour; wger is hidden.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-766", "sev": "P4", "category": "Apps & catalog", "group": "FIXED-BY-LATER-WORK", "evidence": "Hub releases after the assets push (felhom.eu 40f07429, 2026-10-01): hub v0.131.0 (2026-10-04) .. v0.136.0 (d4be9f6f, 2026-10-05). The build copies website assets: felhom.eu scripts/build-hub.sh:98 `cp \"${WEBSITE_ASSETS_DIR}\"/*-logo.svg \"${BUILD_DIR}/assets/\" 2>/dev/null || true`, and the hub build workspace /mnt/5_hdd/felhom.eu/build/felhom-hub/workspace/assets/ holds radicale-logo.svg + 3 screenshots (also karakeep, dawarich) dated Oct 5 14:28; hub/Dockerfile:27 `COPY assets/ /usr/share/felhom/assets-seed/`. Not checked: what a live box shows (no machine access).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-768", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "Fit-check decline recorded in felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md (per row); nothing in source to fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Grimoire is not built because upstream rules out public exposure and ships no image for v1.x; the row only watches for that to change.", "cost": "A periodic re-read of upstream at each catalog campaign.", "if_never": "Nothing; Karakeep covers bookmarks. The re-read can live in the next catalog campaign's checklist instead of an open row.", "pick": "close-as-accepted"}, "minutes_spent": 1}
{"id": "R-769", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "New-app idea waiting on an operator decision (a fork as new upstream, after R-767). No source to check.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-770", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "New-app idea waiting on the operator's go/no-go (CC recommends not building). No source to check.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-771", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "New-app idea waiting on the operator's go/no-go. No source to check.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-779", "sev": "P4", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Live proof gap on the real Cloudflare tunnel needing the operator's phone off wifi; not checkable from source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-781", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog@917a779 scripts/test_gate_decoys.py:498 `sh([\"git\", \"clone\", \"-q\", \"file://\" + ROOT, cat], cwd=ROOT)` clones the REAL records while the stand-in sibling holds only :502-505 `proof.txt`; real records cite sibling evidence (grep -c 'felhom.eu/documentation': onboarding/karakeep.md 45, dawarich.md 43, radicale.md 40). Last change to the file (96829d0, 2026-10-02) added family-gate cases only. Test not run here (it can reach a registry).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": "scripts/test_gate_decoys.py (onboarding_cases)", "change": "After the clone, delete the real onboarding/<app>.md records (all but _TEMPLATE.md and the exempt wger.md the cases use) from the scratch clone, so genuine cases judge only the records they build; alternative: point the stand-in sibling at the real felhom.eu documentation tree read-only.", "test": "Run scripts/test_gate_decoys.py: the four genuine onboarding cases go from FAIL to ok, all decoys still refused; the real check-onboarding gate stays green.", "minutes": 30}, "not_worth": null, "minutes_spent": 6}
{"id": "R-786", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog@917a779 onboarding/sparkyfitness.md has 8 '| open' rows, incl. :18 0.5, :20 0.7, :28 1.6, :29 1.7, :58 5.4, :68 8.3 (plus 0.1 licence = R-784, 2.1 = R-807). Most need live measurement (runtime internet, phone sign-in, second memory watch).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-793", "sev": "P4", "category": "Business & legal", "group": "NOT-WORTH-IT", "evidence": "Watch item from felhom.eu/documentation/audits/licences-2026-10-02/TABLE.md; nothing wrong as the catalog runs them (row's own reading).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Four apps ship enterprise/BUSL code that is off as Felhom runs them; the row reminds us never to enable EE features or the -enterprise meilisearch image.", "cost": "Keeping a standing open row; the real guard would be a line in the licence table / REUSE.md read on each major.", "if_never": "Nothing changes unless someone turns on an EE feature; the rule survives in the licence audit.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-794", "sev": "P4", "category": "Business & legal", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog@917a779 seven redis 7 images: dawarich/docker-compose.yml:164 `image: redis:7.4-alpine`, docmost:86, immich:123, outline:88, nextcloud:103, paperless-ngx:125, romm:136 `image: redis:7-alpine`. Moving each needs a harness-proven ladder step (7 apps).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-796", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "app-catalog@917a779 templates/metube/.felhom.yml:19 `family_gate: true` with no family_gate_except (by design, per row).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "MeTube's browser/phone 'send to MeTube' helpers cannot pass the family gate; households paste links in the page.", "cost": "A per-member token the gate accepts on /add only - a new design.", "if_never": "Households use the page; the helpers stay unusable. Reopen if a household asks.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-797", "sev": "P4", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "app-catalog@917a779 scripts/check-family-gate.py:126 `print(\"family-gate: rule 3 NOT CHECKED — no felhom.eu sibling with a baked golden at %s\" % sibling)` and :141 ' — rule 3 (golden >= 0.287.0) NOT CHECKED here' - the gap is stated, and the pre-push hook (with sibling) checks it.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "CI's single-repo clone cannot check rule 3 of the family-gate gate; it says NOT CHECKED, and the pre-push hook checks it.", "cost": "Giving catalog CI a felhom.eu sibling checkout (credentials, workflow change).", "if_never": "A push that bypasses the hook (--no-verify is forbidden) could skip rule 3; CI stays honest about it.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-798", "sev": "P4", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog@917a779 templates/grimmory/docker-compose.yml:29 ` - SWAGGER_ENABLED=false` still present.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": "templates/grimmory/docker-compose.yml", "change": "Remove the dead SWAGGER_ENABLED line (or rename to API_DOCS_ENABLED=false, which v3.5.0 reads and defaults to false). No image: line moves.", "test": "catalog_gates.py --fast green; grep shows no SWAGGER_ENABLED. Note: a compose change syncs to boxes running grimmory and recreates the container, so the row's 'on the next Grimmory step' timing is reasonable.", "minutes": 10}, "not_worth": null, "minutes_spent": 2}
{"id": "R-799", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog@917a779 scripts/upgrade_fixtures_box.py:2183 `data=json.dumps({\"url\": self.URL, \"quality\": \"best\", \"format\": \"any\", \"auto_start\": True}), method=\"POST\")` - no download_type.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": "scripts/upgrade_fixtures_box.py (class MeTube.seed)", "change": "Add \"download_type\": \"video\" to the POST /add body.", "test": "Python syntax/import check of the fixture module; real proof rides the next MeTube bench/box walk (POST /add 200).", "minutes": 10}, "not_worth": null, "minutes_spent": 2}
{"id": "R-804", "sev": "P4", "category": "Apps & catalog", "group": "NOT-WORTH-IT", "evidence": "app-catalog@917a779 templates/plant-it/docker-compose.yml:12 `image: msdeluise/plant-it:0.10.0`; templates/plant-it/.felhom.yml:23-26 \"The compose below is deliberately LEFT AS-IS (it pins `msdeluise/plant-it:0.10.0`, a repository ... It is not worth fixing\" and `lifecycle: abandoned`.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "plant-it's image repository does not exist; the template is already abandoned and not installable, and no box runs it.", "cost": "An operator choice to hide the template entirely (small catalog edit).", "if_never": "Nothing; the template is already documented as not installable on purpose.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-805", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog-felhom.eu scripts/check-volume-persistence.py:342 'if m[\"class\"] == \"named-declared\" and m.get(\"files\", 0) == 0:' — only named volumes judged empty; binds not. Last change 917a779 (R-788). The rule change itself is small, but it flips Grimmory/komga/paperless-ngx/radarr/sonarr to UNDETERMINED and needs a live re-sweep to regenerate the verdict tables; the row also asks for a decision.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-806", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "app-catalog-felhom.eu scripts/check-volume-persistence.py:586 'code = _sh(a + [f\"http://{ip}:{port}{path}\"], timeout=40)' — plain http always; scheme reading exists only in scripts/upgrade_boxport.py:33 LB_SCHEME_RE and scripts/upgrade-test.py:407. The gramps-web no-answer half is not checkable from source.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["scripts/check-volume-persistence.py", "scripts/test_check_volume_persistence.py"], "change": "Make routed_ports return the traefik loadbalancer.server.scheme (reuse upgrade_boxport LB_SCHEME_RE) and have the GET exercise use that scheme with curl -k for https. The gramps-web :5000 non-answer stays a separate live look (narrow the row to it).", "test": "Unit test: a compose with scheme=https label yields an https:// URL in the built curl argv; decoy without the label yields http://.", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-807", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Per-app upload seeds for 13 apps (claper, crafty-controller, dawarich, docmost, gramps-web, immich, outline, sparkyfitness, tandoor, vikunja, wger, wishlist, zipline) + plex/wanderer; each needs a live fixture run. Gate rule at scripts/check-volume-persistence.py:342 still makes empty declared volumes UNDETERMINED (917a779).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-814", "sev": "P4", "category": "Hub & operator", "group": "UNCHECKED", "evidence": "Live Hetzner console state (box 611421 status); not visible in source. Operator action only.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-815", "sev": "P4", "category": "Backup & restore", "group": "UNCHECKED", "evidence": "First GC completion on felhom-offsite is PBS server-side live state; grep of documentation found no GC completion record (DIAG-backup-missed-2026-07-26.md:43 'prune/GC history NOT COLLECTED').", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-816", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Needs a live exercise of six failure classes on a scratch guest; no source change can close it.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-817", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu documentation/architecture/09-update-architecture.md:619 '56. **A box keeps the controller image it runs and the one before it** (the self-update's roll-back target)'. Source disagrees: felhom-agent internal/localapi/controllerswap.go:240 'st.Previous, st.Current = prev, prev' (prev = image running when the swap began); felhom-controller controller/internal/stacks/controller_image_retention.go:21-22 '...the self-update's own roll-back needs only the running image... \"The previous\" is kept for a hand roll-back.' The decision text is the wrong one.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["documentation/architecture/09-update-architecture.md"], "change": "Add a dated correction under decision 56: the swap rolls back to the image running when the swap began (controllerswap.go Swap/rollback); the kept previous image is for a hand roll-back. Do not rewrite the ruling itself.", "test": "Doc only: grep the corrected line; repo_gates.py --fast passes.", "minutes": 15}, "not_worth": null, "minutes_spent": 8}
{"id": "R-818", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu hub/CHANGELOG.md:759 '## v0.109.0 — the Backup card told every operator that every customer had no backups (2026-08-30, R-331)' and :766 '(R-330 is a nightly false alarm ...; R-331 is a hub display ...)'; OPEN-ITEMS.md:268/274 have R-330/R-331 as Disk health Phase 2/3. Line numbers moved from the row's 526-538 to 759-771. No correction line present.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/CHANGELOG.md"], "change": "Add a dated correction note under the v0.109.0 entry (and the hub CHANGELOG mentions at :695/:701) naming the real closed rows from CLOSED-ITEMS.md. Also present in felhom-controller/CHANGELOG.md:3107 and :3234 (R-330/R-331 for v0.224.0/v0.225.0) — a second repo; either note it there too or narrow the row.", "test": "Doc only: grep the correction; gates pass.", "minutes": 25}, "not_worth": null, "minutes_spent": 6}
{"id": "R-819", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "Ran python3 scripts/check_stands.py (read-only): still convicts e.g. 'fail.stolen-machine: register id R-281 is not in OPEN-ITEMS.md', 'fail.customer-self-restore: register id R-356 ...'. grep 'stands' in scripts/repo_gates.py and .gitea/workflows/gates.yml: no hit. Last commit on the script 6088afcb.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/check_stands.py", "scripts/repo_gates.py", "where-felhom-stands.yaml (or its source)"], "change": "Let rule 3 accept an id found in CLOSED-ITEMS.md (and check the stand's status agrees), fix the R-273/R-356 dangling ids, register the gate in repo_gates.py with a decoy.", "test": "Run check_stands.py green; a decoy stand citing a non-existent id must fail; test_repo_gates.py passes.", "minutes": 60}, "not_worth": null, "minutes_spent": 6}
{"id": "R-832", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Deferred roadmap item: a third-location copy of ep0 is money + operator decision (decision 71). Nothing in source to change.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-844", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Needs a household timeline on the controller, which does not exist (row: 'when the box gets a household timeline'). A new surface, not a fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-855", "sev": "P4", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu hub/cmd/hub/main.go:450 'logger.Printf(\"[INFO] osupdates: the Docker engine set is approved only by the operator, after %d healthy ring-0 night(s)\", osSvc.DockerNights)' prints the raw value; internal/osupdates/service.go:169 'negative means none'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/cmd/hub/main.go", "hub/internal/osupdates/service.go"], "change": "Add a small helper (e.g. osSvc.DockerNightsEffective()) mapping negative→0 and 0→2, and print that in the start log.", "test": "Unit test of the helper for -1, 0, 3; red-proof by printing the raw value.", "minutes": 20}, "not_worth": null, "minutes_spent": 4}
{"id": "R-856", "sev": "P4", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Row is marked an operator design question (crash-restart suppression for app mails); not a defect yet.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-857", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu scripts/golden_currency_gate.py:146 'EVIDENCE_RE = re.compile(r\"^golden-(\\d+)\\.(\\d+)\\.(\\d+)-\\d{4}-\\d{2}-\\d{2}$\")' and :237-238 'found.sort() / return found[-1]' — two dirs with the same version tie-break by name, not by bake time; a '-rebake' suffix dir (documentation/tests/golden-0.292.0-2026-10-04-rebake/) does not match the regex at all, so the re-bake is never read.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/golden_currency_gate.py", "scripts/test_golden_currency_gate.py", "documentation/runbooks/RUNBOOK-manual-build.md (re-vouch line)"], "change": "Have newest_baked accept an optional suffix after the date and, for equal versions, prefer the newest bake-log timestamp (or refuse two dirs for one version); add 're-vouch at once after a same-version re-bake' to the runbook.", "test": "Test with two tmp dirs of one version holding different GOLDEN_SHA256 lines: the newer sha must be reported; red-proof against today's code.", "minutes": 45}, "not_worth": null, "minutes_spent": 7}
{"id": "R-878", "sev": "P4", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Next action is 'measure a large volume first' — a live measurement; the fix direction is a behaviour change to the catch-up.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-881", "sev": "P4", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu scripts/felhom-host-install.sh: grep 'felhom-priv-apply' → no hit anywhere in the installer (uninstall does not remove it); :1679 '+ guest-hook snippet under /var/lib/vz/snippets/ (agent-installed at runtime)' still says runtime-installed.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh", "CHANGELOG"], "change": "Add an rm -f of /usr/local/sbin/felhom-priv-apply to the uninstall step (tolerate-absent, like felhom-pbs-apply at :1161) and fix the :1679 comment; ships at the next installer tag.", "test": "bash -n; --uninstall --dry-run output lists the removal; installer gates pass.", "minutes": 25}, "not_worth": null, "minutes_spent": 4}
{"id": "R-884", "sev": "P4", "category": "Monitoring & notifications", "group": "UNCHECKED", "evidence": "Live ArgoCD diff on DooPlex (forbidden to touch here). Related: homelab-manifests mon-system/monitoring.yaml:399-426 is the same prometheus Deployment R-211 concerns.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-885", "sev": "P4", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "On main (b018ca90) scripts/repo_gates.py runs no test_*.py suite. NOTE: the felhom.eu working tree holds UNCOMMITTED work for exactly this row by another session: '?? scripts/script_tests_gate.py' (docstring 'every Python test suite under scripts/ runs on every push (R-885)'), '?? scripts/test_script_tests_gate.py', ' M scripts/repo_gates.py' (registers 'script-tests'). Not fixed until pushed.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/script_tests_gate.py", "scripts/test_script_tests_gate.py", "scripts/repo_gates.py"], "change": "Finish and push the in-progress script_tests_gate.py (walks scripts/ for test_*.py, exit-code verdict, nesting guard) registered in repo_gates.py; coordinate with the session that owns the dirty tree.", "test": "test_script_tests_gate.py decoys (a failing suite, no suite found); one CI run green.", "minutes": 30}, "not_worth": null, "minutes_spent": 6}
{"id": "R-30", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Design change (presence from the Dir-2 long-poll instead of the report clock), size M; no commit with R-30 after aa9c08f0 (filing).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-31", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu hub/internal/web/configs.go:1594 'd, err := s.offsite.ProvisionOffsite(ctx, cfg.CustomerID, in)' still in-request; :1584 detaches from the request context (mid-cancel fixed) but no async/status card.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-35", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller controller/internal/report/config_refresh.go:65 'config-refresh: applied config_version=%d — self-restarting to load it'; sessions are in-memory only: controller/internal/web/auth.go:259-260 's.sessions[token] = &session{'. Hot-apply or persisted sessions is a design change with security weight.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-49", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog-felhom.eu templates/immich/.felhom.yml:28-34 backup block has no cache/volume exclusion; row itself says a capture-set exclusion needs its own ruling (data-loss-shaped).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-50b", "sev": "P3", "category": "Box system & updates", "group": "FIXED-BY-LATER-WORK", "evidence": "Claim 'fetched via fetch_raw from raw/branch/main — no tag, no pin' no longer true: bee68484 (installer v1.23.0, R-110/R-183) pinned fetch_raw to the vouched agent tag — felhom.eu scripts/felhom-host-install.sh:533 '\"$GITEA_BASE/$GITEA_OWNER/$AGENT_REPO/raw/tag/v$ART_AGENT_VER/$path\" \\' (leg b). Leg (c)-like signed delivery: felhom-agent c9fa2e7 (R-840 config bundle) and configs/test_felhom_config_bundle.py:264 covers /usr/local/sbin/felhom-pbs-apply. Residual worth one line if kept: the 0440 sudoers drift visibility note.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 7}
{"id": "R-78", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "An owed operator decision + spike (local_api authority); not a code defect.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-79", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller controller/internal/monitor/healthcheck.go:100 'fmt.Sprintf(\"SSD disk usage critical: %.0f%%\"', :213 'Protected container not running: %s'; rendered raw at controller/internal/web/alerts.go:241 'Message: issue, // ON THE WIRE ... not ours to translate; slice 3'. Whole-surface, seam needs a spike.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-118", "sev": "P3", "category": "Storage & devices", "group": "STILL-TRUE-SMALL", "evidence": "felhom-agent internal/localapi/disks.go:401 'if total, used, okc := statfsCapacity(d.MountPath); okc {' — no device-presence guard on the union path; devicePresent exists (disks.go:985) and is used just above (:381) for BoundUnderParent.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-agent", "files": ["internal/localapi/disks.go", "internal/localapi/disks_device_presence_test.go"], "change": "Only statfs the mount when s.devicePresent(d.MountPath) is true (else leave capacity zero/unknown); put statfsCapacity behind a seam var for the test.", "test": "Test: absent device + statfs seam returning root's numbers → row has no capacity; present device → capacity set; red-proof by removing the guard. Ships in the next agent release.", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-121", "sev": "P3", "category": "Box system & updates", "group": "FIXED-BY-LATER-WORK", "evidence": "3d7a2761 (hub v0.135.0, R-530/R-604 'boxes left behind listed and alarmed'): felhom.eu hub/internal/osupdates/service.go:79 'EventAgentBehind = \"agent_behind\" // warning, operator' with :180 'AgentBehindAfter: a box runs an agent older than the vouched one this long → an operator alarm' (7 d window, the staleness window the row asked for).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-126", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller controller/internal/web/handler_export.go:377-386 storageDriveList() appends every s.settings.GetStoragePaths() entry with no IsNetwork() filter; the predicate exists at controller/internal/settings/settings.go:618 'func (p StoragePath) IsNetwork() bool { return p.Kind == StorageKindNetwork }'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/handler_export.go", "controller/internal/web/handler_export_test.go"], "change": "Skip IsNetwork() paths in the export-destination list and refuse them in isValidDrivePath for the export POST (keep scanning for import if wanted, via a separate list).", "test": "Handler test with one drive + one network path: destination list has only the drive; export POST to the NAS path is refused; red-proof without the filter. Needs a controller release.", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-127", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Leg (a) still true: grep 'data_key: true' in app-catalog-felhom.eu templates → only adventurelog, dawarich, homebox, papra, sparkyfitness; n8n N8N_ENCRYPTION_KEY, wanderer POCKETBASE_ENCRYPTION_KEY, calcom CALENDSO_ENCRYPTION_KEY, bookstack APP_KEY unflagged (templates/n8n/.felhom.yml:38 etc.). Leg (b) (regenerated DB password vs restored PGDATA) needs a design choice. Leg (a) alone is a ~45-min catalog change + label/flag agreement gate and could be split out.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-130", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu scripts/felhom-host-install.sh:348 'HARD_MIN_LVM_GIB=120 # a useful appliance won't fit below this on local-lvm' and :1760 '... || log_warn \"local-lvm free ~${free_gib} GiB < hard min ${HARD_MIN_LVM_GIB} GiB\"' — warns only.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh"], "change": "Take the cheap honest branch: rename to RECOMMENDED_MIN_LVM_GIB and reword the warning to 'below the recommended …' (making it refuse would change install behaviour and needs a ruling).", "test": "bash -n; installer gates; grep shows no 'hard min' left. Ships at the next installer tag.", "minutes": 20}, "not_worth": null, "minutes_spent": 4}
{"id": "R-132", "sev": "P3", "category": "Security & access", "group": "UNCHECKED", "evidence": "Whether HUB_PW was rotated is out-of-band operator state; not visible in source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-136", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu hub/internal/web/server.go:857 'Name: \"hub_session\",' and readers at server.go:806, :896, :918 and apps.go:351.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["hub/internal/web/server.go", "hub/internal/web/apps.go", "a server test"], "change": "Introduce a const sessionCookieName = \"__Host-hub_session\" and use it at all five sites (Path=/, Secure, no Domain already hold). Every operator logs in once more; plain-HTTP browser access stops (Basic auth unaffected).", "test": "Test: login response sets __Host-hub_session with Secure+Path=/ and no Domain; a request with the old name is not a session.", "minutes": 30}, "not_worth": null, "minutes_spent": 4}
{"id": "R-137", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller controller/internal/cloudflare/waf.go:18 'globalRuleDesc = \"[felhom-geo] Global\"', :21 'appRuleDescPrefix = \"[felhom-geo] app:\"' — still not namespaced. Two-repo M change.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-138", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller controller/internal/infra/infra.go:157-159 writes CF_DNS_API_TOKEN when d.CFAPIToken != \"\"; no shared-zone guard in hub (grep shared.zone: none). Needs a policy decision first; no shared zone exists today.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-179", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu scripts/felhom-host-install.sh uninstall section :1121-1157 handles felhom-shared-parent and umounts under /mnt/felhom-drives, but grep 'x2ddrives|automount' → no hit: the per-share mnt-felhom\\x2ddrives-*.mount/.automount units are never stopped or removed.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh"], "change": "In uninstall step 4c, before the umount loop: stop+disable every mnt-felhom\\x2ddrives-*.automount/.mount unit, rm their files from /etc/systemd/system, daemon-reload (tolerate-absent; still never umount -l/-f).", "test": "bash -n; --uninstall --dry-run on a fixture listing; a demo-box uninstall run is the real check at the next installer tag.", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-180", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu scripts/felhom-host-install.sh:1800 'if pvesm status --storage \"$ARCHIVE_STORAGE\" ...' checks existence only; PVE_STORAGES=(local local-lvm felhom-pbs) at :322; no ARCHIVE_STORAGE ∈ PVE_STORAGES assertion.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh"], "change": "In the same pre-flight block, die (byo) / die (appliance) if ARCHIVE_STORAGE is not in PVE_STORAGES, with a message naming --acl-storages.", "test": "bash -n; a dry-run with --archive-storage felhom-backup must die in pre-flight; with local passes.", "minutes": 25}, "not_worth": null, "minutes_spent": 5}
{"id": "R-190", "sev": "P3", "category": "Box system & updates", "group": "NOT-WORTH-IT", "evidence": "Mitigation still in source: felhom-agent cmd/felhom-agent/main.go:656 'store-grant: GRANT WAS MISSING AND HAS BEEN SELF-REPAIRED — investigate the loss (R-190)'. Mechanism unexplained since 2026-08-03.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A storage permission vanished once on demo-felhom in August and nobody knows why. Since agent 0.124.1 the agent puts it back by itself and mails the operator when it happens.", "cost": "Finding the cause needs live experiments with guest rebuilds and permission caches on a Proxmox host — hours, maybe days, with no guaranteed answer.", "if_never": "The box repairs the permission within one check cycle and the operator gets one e-mail each time; a recurrence would be visible and would reopen the question.", "pick": "close-as-accepted"}, "minutes_spent": 4}
{"id": "R-200", "sev": "P3", "category": "Backup & restore", "group": "FIXED-BY-LATER-WORK", "evidence": "The remaining half (customer-facing recovery-code form: yell → R form → preview) shipped as the recovery screen: felhom-controller 636c51e 'R-193: the recovery screen — unlocking, and only unlocking (v0.200.0)'; controller/internal/web/templates/recovery.html:82 '<form id=\"unlock-form\" method=\"POST\" action=\"/recovery/unlock\" autocomplete=\"off\">', routed at internal/web/server.go:602. Plumbing half was 1b1366b (v0.196.0). The 64-hex inject-password route (server.go:783) stays a deliberate DR fallback with no form.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-211", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "homelab-manifests (/home/kisfenyo/git/homelab-manifests @87dfc29) mon-system/monitoring.yaml:420 'image: prom/prometheus:v3.15.0', :426 '--web.enable-lifecycle'; grep 'reload|checksum/config' → none. The manifest edit is small, but it rolls the production Prometheus on DooPlex (operator territory) and the same Deployment is OutOfSync per R-884 — do the two together.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-231", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Owner operator; DooPlex /opt/backup/scripts remains host state. Partially touched by cea8502f (scripts/hub-db-backup versioned in felhom.eu, cites R-231) but that covers only the hub-DB push, not /opt/backup/scripts or the same-disk/no-off-site facts.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-235", "sev": "P3", "category": "Install & onboarding", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu c033b3b6 'ISO 1.28.0 source: the console stops showing the pairing code once bound (R-535)'. scripts/iso/felhom-bootstrap.sh:538: `print_bound_banner # R-535: replace the pairing code on the console with the truth`. Same defect already CLOSED twice in CLOSED-ITEMS.md as R-535 (line 232) and R-214 (line 200, 'proven on a fresh install').", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-240", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/backup/offbox.go:1168: `warns = append(warns, \"Sikeres — nincs mentésre jelölt alkalmazás\")`; web/handlers.go:1068-1069 returns lastWarning verbatim when toggledCount < 1, so the customer still sees 'Sikeres'. Run now also records warnKind=OffboxWarnNoAppsSelected (R-553), so the page no longer depends on the sentence for new runs.", "dup_of": null, "unique_facts": "handlers.go:1064-1066 comment: R-557 must not TRANSLATE the producer until R-570 closes (legacy kind=='' fallback matches the lowercase substring 'nincs mentésre jelölt alkalmazás'). A Hungarian rewording that keeps that lowercase substring stays safe for legacy boxes.", "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/backup/offbox.go", "controller/internal/backup/offbox_test.go", "controller/internal/backup/offsite_diag_test.go", "controller/internal/backup/r553_offsite_quota_test.go", "controller/internal/web/r553_stale_note_test.go"], "change": "Replace the producer string at offbox.go:1168 with wording that drops 'Sikeres' but keeps the lowercase marker substring, e.g. 'Ez a futás semmit nem mentett: nincs mentésre jelölt alkalmazás'; update the tests that pin the literal.", "test": "go test ./internal/backup ./internal/web -run 'Offbox|R553|Diag' (unit, no docker); red-proof: a test asserting the warning does not start with 'Sikeres' fails on old code.", "minutes": 45}, "not_worth": null, "minutes_spent": 8}
{"id": "R-242", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/scripts/golden_currency_gate.py:23-24: 'It does **NOT** check that the golden was **VOUCHED**, because the vouched version lives ONLY in the hub's `hub_settings` table'; :40 'That vouch half is STILL open after 2026-09-13'. Last gate commits 5ef0f52b/ae59c31a did not add a vouch check.", "dup_of": null, "unique_facts": "Only the vouch half remains; it needs a hub-reading check (design: shape (c) hub-side checker) and cannot be a --fast gate. Operator ruling 2026-09-13 (goldens weekly + waiver file) reduces the urgency; candidate for NOT-WORTH-IT if the operator accepts.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-244", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub: no cascade leg touches app_log_issues — only writers are internal/store/telemetry.go:176-209 (upsert), :537 `DELETE FROM app_log_issues WHERE last_seen < ?`, :556/:575 operator deletes by app/id. cmd/hub/main.go:1004: `if n, err := s.PruneStaleIssues(time.Now().Add(-30 * 24 * time.Hour))` (since a757bee0, hub v0.4.0).", "dup_of": null, "unique_facts": "Not in the row: a 30-day stale-issue prune has existed since hub v0.4.0, so ORPHAN rows (only torn-down customers) age out on their own once not seen for 30 days — the 'accumulates one venue at a time' claim holds only for the SHARED rows, which keep a deleted customer's id in affected_customers while a live customer keeps reporting. The remaining fix is JSON de-referencing in the cascade + red-proof.", "small_fix": null, "not_worth": null, "minutes_spent": 7}
{"id": "R-250", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub/internal/offsite/offsite.go:71-72: `var defaultScanBackoff = []time.Duration{2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second, 30 * time.Second}`; scanner.go:40 dials plain \"tcp\" (no A-record preference); no R-250 commit.", "dup_of": null, "unique_facts": "Not in the row: cmd/hub/main.go:617 `WriteTimeout: 60 * time.Second` — the ~60 s ladder already meets the server write deadline, so lengthening the ladder inside the POST needs a per-request deadline lift (internal/api/wait.go:58 shows the pattern) or async provisioning; that is why it is not a one-line change.", "small_fix": null, "not_worth": null, "minutes_spent": 7}
{"id": "R-251", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/backup/offbox_inventory.go:102-108: loops `for _, tag := range sn.Tags` and adds every non-empty tag to `newest` — no filter for 'felhom-offbox'. The marker filter exists only on the restore list (internal/web/offsite_restore_list.go:37 `const offboxMarkerTag = \"felhom-offbox\"`), not on the recovery inventory (web/recovery_handlers.go:175 `data[\"InvApps\"] = inv.Apps`).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/backup/offbox_inventory.go", "controller/internal/backup/offbox_inventory_test.go"], "change": "In offsiteNewestPerTag skip the 'felhom-offbox' marker tag (move the constant into package backup and reuse it from web); consider whether '_shares' should render as a named row or be skipped.", "test": "Extend inventoryFixture (offbox_inventory_test.go:22) so snapshots carry tags [felhom-offbox, <app>]; assert OffsiteInventoryList returns only the app rows and one size call per app; red-proof on current code (marker row appears).", "minutes": 40}, "not_worth": null, "minutes_spent": 9}
{"id": "R-255", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller: still no page-wide runtime secret-sentinel test — `grep -l sentinel internal/web/*_test.go` hits only edge_safe_status_test.go, i18n_parity_test.go, recovery_test.go; controller/scripts/secret_in_markup_gate.py remains the only all-template net. No commit cites R-255.", "dup_of": null, "unique_facts": "Not in the row: the i18n parity test (internal/web/i18n_parity_test.go, TestI18nParity:516, 133 fixtures in testdata/i18n_parity) now builds per-page render data for most pages — the per-page fixture scaffolding this row priced as the main cost largely exists; a secret-sentinel pass could reuse it, making this cheaper than estimated (still > 1 h).", "small_fix": null, "not_worth": null, "minutes_spent": 7}
{"id": "R-257", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/i18n/locales/hu.json:1409: `\"flash.offbox.not_orphaned\": \"Az offsite tároló nincs elárvult állapotban.\"`, used at internal/web/offbox_handlers.go:317 (row cited :270); also hu.json:1216 err.backup.az_offsite_tarolo_nincs_elarvult_allapotban from backup/offbox.go:385.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/i18n/locales/hu.json", "controller/internal/i18n/locales/en.json"], "change": "Rewrite flash.offbox.not_orphaned (hu + en) to say what the customer tried, that it does not apply now, and where to look, without 'offsite'/'elárvult', e.g. 'A távoli mentés rendben van, nincs mit félretenni. Ha gondod van vele, írj nekünk.'; wording sign-off from the operator (owner Viktor).", "test": "Run the i18n parity/key tests (go test ./internal/i18n ./internal/web -run I18n) and an ASCII grep for 'elarvult' in the flash value as negative control.", "minutes": 25}, "not_worth": null, "minutes_spent": 5}
{"id": "R-262", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/hub/internal/api/handler.go:727-729: '// hostBackup / hostRestoreTest mirror the agent's hub.Backup / hub.RestoreTest wire // contract field-for-field'; struct hostRestoreTest (handler.go:746-761) has no mount_parity/mount_inventory, while felhom-agent/internal/hub/report.go:492-493 `MountParity string json:\"mount_parity,omitempty\"` / `MountInventory []string`. grep finds no mount_parity anywhere in hub Go code.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/internal/api/handler.go", "hub/internal/api/host_test.go"], "change": "One-repo fix: narrow the comment to say hostBackup is field-for-field and hostRestoreTest is a deliberate SUBSET (lists mount_parity/mount_inventory as not modelled), and add a hub test that names the two agent fields as known-unmodelled so a future addition must edit it. Adding the fields + fixture is a two-repo change (byte-identical golden) and stays a separate choice.", "test": "go test ./internal/api -run HostReport (hub, no docker).", "minutes": 30}, "not_worth": null, "minutes_spent": 7}
{"id": "R-269", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom-agent/internal/localapi/tokenstore.go:173-176: `if vmid, ok := s.byHash[want]; ok { if subtle.ConstantTimeCompare(...) == 1 { return vmid, true } }` — a superseded token's hash is still a direct hit; reload happens only on a miss (:180). Last change to the file f31a76f (v0.63.0, the reload-on-miss itself); no R-269 commit.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-agent", "files": ["internal/localapi/tokenstore.go", "internal/localapi/tokenstore_test.go"], "change": "On a map hit, also stat the store and reload when its size differs from loadedSize before answering (one cheap stat per auth), so a rotated-out token is rejected without depending on an unrelated miss.", "test": "Add TestTokenStore_RotatedOutTokenRejectedFirst: mint A, re-mint B from a second TokenStore instance on the same file, look up A FIRST on the long-lived instance -> want false. Red on current code (row says red-proved).", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-270", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/internal/bootstrap/bootstrap.go:255: `if cfg == nil || cfg.LocalAPI.Endpoint != \"\" {` (fill-only, never refreshes); DetectEndpointDrift (bootstrap.go:369-399) compares only the endpoint. No R-270 commit.", "dup_of": null, "unique_facts": "Blocked in substance on R-78 (OPEN, OPEN-ITEMS.md:323) — the operator's authority ruling (auto-reconcile vs detect-only) decides which fix is right. The rotation recipe itself is documented in memory 'local-API token rotation needs TWO files + TWO restarts'.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-271", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/internal/channelhealth/checker.go:152: `if prev != \"\" && prev != \"up\" {` (unseeded->up is silent); checker.go:87 alert text still says '(re-bootstrap)'. No R-271 commit.", "dup_of": null, "unique_facts": "Fix needs a persisted last state or a hub-seeded state (a new mechanism), so not an under-an-hour change.", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-274", "sev": "P3", "category": "Box system & updates", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu eb600872 'R-297: installer compares a local golden against the manifest before using it'. scripts/felhom-host-install.sh:3074: `if golden_local_matches_manifest \"$GOLDEN_VOLID\"; then` (digest vs ART_GOLDEN_SHA, else baked marker vs ART_GOLDEN_VER; otherwise ignores the local golden and fetches, or dies if the operator named it, :3080). Line numbers differ from the triage note (2855-2905).", "dup_of": null, "unique_facts": "The BYO-disclosure half ('say what the install REUSES') was not checked; if wanted, it is a separate small wording row.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-275", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/felhom-host-install.sh:1059: `for _cfgbak in \"${agent_cfg}\".bak*; do [[ -e \"$_cfgbak\" ]] && run rm -f \"$_cfgbak\"; done` — glob still misses agent.json.campaign8-before, .campaign9-prev, .pre-e-target-move, .pre-prunegate.bak; :814 still claims 'config (+ its .bak backups)'. No R-275 commit.", "dup_of": null, "unique_facts": "The uid-reuse half (new service account inheriting uid 999) is not covered by the small fix; purging by directory makes it moot for /etc/felhom-agent.", "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh", "scripts/hostinstall_gates.py"], "change": "Purge every sibling `\"${agent_cfg}\".*` (and then rmdir/rm the config dir) instead of `.bak*`; fix the WIPED line wording.", "test": "Add a hostinstall_gates.py check (or a bash harness under a temp dir with FELHOM paths) that creates agent.json.campaign8-before and agent.json.pre-prunegate.bak and asserts run_uninstall --dry-run lists both; red on current glob.", "minutes": 40}, "not_worth": null, "minutes_spent": 6}
{"id": "R-276", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/felhom-host-install.sh: no reference to wg-felhom/wg-quick anywhere (grep 'wg-quick\\|wg-felhom' empty); _uninstall_statement (:807-845) lists neither in WIPED nor KEPT. The tunnel is agent-managed: felhom-agent/internal/wgtunnel/manager.go:31 `confDest = \"/etc/wireguard/wg-felhom.conf\"`, :35 `unit = \"wg-quick@wg-felhom\"`.", "dup_of": null, "unique_facts": "Hub-side peer deregistration (hub/internal/store/wg_operator.go) is a second repo; the small fix covers the host side and names the hub peer under KEPT.", "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh", "scripts/hostinstall_gates.py"], "change": "In run_uninstall (full scope): stop+disable wg-quick@wg-felhom and remove /etc/wireguard/wg-felhom.conf via run(); add it to WIPED, and add a KEPT line 'the hub-side WireGuard peer registration — remove it in the operator UI'.", "test": "Static gate in hostinstall_gates.py asserting the uninstall path names wg-quick@wg-felhom and the statement mentions it; --dry-run output check.", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-277", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "Part (a) FIXED by felhom.eu f5c9411e 'R-331 (hub half): the Backup card reads `offsite`, not the dead `backup` fields (v0.109.0)' (hub/internal/web/backup_card.go uses fmtBytesAuto :135-142). Part (b) still true: hub/internal/web/offsite_box.go:54 `return fmt.Sprintf(\"%.1f GB\", float64(b)/float64(int64(1)<<30))` — a 162 KB repo renders 0.0 GB.", "dup_of": null, "unique_facts": "Part (c) (stale offsite_delivery_stuck event read as current state) was NOT verified; if it is still wanted it should become its own row.", "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/internal/web/offsite_box.go", "hub/internal/web/offsite_box_test.go"], "change": "For UsageStr use fmtBytesAuto when the usage is below 1 GB (keep GB for the quota and the bar), so a non-empty repo never renders as 0.0 GB.", "test": "Unit test: 162*1024 bytes -> not '0.0 GB'; 0 bytes and >1 GB unchanged (go test ./internal/web -run Offsite).", "minutes": 25}, "not_worth": null, "minutes_spent": 8}
{"id": "R-282", "sev": "P3", "category": "Install & onboarding", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu 4d6ec7c 'hub v0.104.0: ... the hub half of the naming (R-295)' + controller v0.211.0 (R-295 CLOSED, CLOSED-ITEMS.md:207) + R-323 hub v0.105.0. hub/internal/notify/templates.go:204: '// R-295, HUB HALF (2026-08-13). ONE NAME PER SECRET, and it is „Beállító kód\".'; hub/internal/claim/engine.go:51 `EmailReenroll EmailKind = \"reenroll\"` (mail names the setup page a rebuilt box shows).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-283", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub/internal/claim/engine.go:7: '// engine: a rotation bumps the generation (single active code) and NEVER clears claimed_at.'; ReissueForReenroll (engine.go:195-212) rotates and mails but leaves the claim set — the hub still shows the customer as claimed after a guest rebuild. No R-283 commit.", "dup_of": null, "unique_facts": "The mail half is now right (EmailReenroll names the setup page, R-295 hub), so the R-282 consequence is gone; what remains is hub/box claim-state reconciliation (design).", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-298", "sev": "P3", "category": "Storage & devices", "group": "UNCHECKED", "evidence": "The template gate is still there: felhom-controller/controller/internal/web/templates/storage.html:364 `if(d.role==='user-data'){` else protected (:368). BUT the agent no longer reclassifies a backup-target drive: felhom-agent/internal/localapi/disks.go:1230-1236 ('WHY THIS IS NOT A ROLE RECLASSIFICATION ... the drive that now holds the whole-guest archives is ALSO the enrolled user-data drive') and disks.go:219/227 report Role from RoleForStorage plus a separate BackupTarget flag (958e54f, agent v0.112.0). storage/role.go:176-186 makes a local-dir with its own non-system device 'user-data'.", "dup_of": null, "unique_facts": "From source, a drive that is both user-data and the backup target should arrive as role 'user-data' and therefore be registrable; the row's 2026-08-10 observation contradicts that. Only a live /api/disks read on a box with that layout settles it.", "small_fix": null, "not_worth": null, "minutes_spent": 10}
{"id": "R-306", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom.eu/scripts/felhom-host-install.sh:418: `$DRY_RUN && return 0` (only DRY_RUN short-circuits _state_put); :1851/:1854 `_state_put dnsmasq_preexisting yes|no` run unguarded in preflight, while :226 says '--preflight-only: ... no state writes' and :1956-1961 guards only customer_id/mode. Blockers R-300 and R-305 are CLOSED.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/felhom-host-install.sh", "scripts/hostinstall_gates.py"], "change": "Make _state_put (and _state_mark) return 0 when PREFLIGHT_ONLY is true, so a preflight-only run writes nothing; the real run's preflight records ownership again.", "test": "Bash harness with STATE_DIR in a temp dir: run the preflight state block with PREFLIGHT_ONLY=true -> no state.json; red on current code. Or a static gate asserting _state_put checks PREFLIGHT_ONLY.", "minutes": 30}, "not_worth": null, "minutes_spent": 6}
{"id": "R-314", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller: StopAbandon is called only from cmd/controller/main.go:244 (CLI) — `grep -rn StopAbandon` shows internal/backup/offbox_abandon.go:374 and that CLI call, no web handler.", "dup_of": null, "unique_facts": "An 'operator-authenticated POST' on the controller needs an operator auth path the controller does not have today (or a hub-relayed command) — a new mechanism.", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-317", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-SMALL", "evidence": "felhom-agent/internal/lanresolver/lanresolver.go:107: `if _, err := os.Stat(\"/usr/sbin/dnsmasq\"); err != nil { // metadata read, no privilege needed` — still probes the dnsmasq-base file. No R-317 commit.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-agent", "files": ["internal/lanresolver/lanresolver.go", "internal/lanresolver/lanresolver_test.go"], "change": "Probe the dnsmasq unit (e.g. /usr/lib/systemd/system/dnsmasq.service or /lib/systemd/system/dnsmasq.service) behind a small stat seam instead of /usr/sbin/dnsmasq.", "test": "Seam test: binary present + unit absent -> install is attempted; unit present -> skipped. Red on current code.", "minutes": 40}, "not_worth": null, "minutes_spent": 4}
{"id": "R-330", "sev": "P3", "category": "Storage & devices", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-agent/internal/hub/report.go:406-425 SmartSummary carries reallocated/pending/offline_uncorrectable + NVMe set only — no 187/188/199 fields. Wire change across agent + hub (+ controller), declared M.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-332", "sev": "P3", "category": "Storage & devices", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Closing condition is live-only (a real degrading disk or an injection through agent /disks -> controller -> hub). Last related commits ea16a21b/2fa1efc narrowed the restart half only; no commit records a live Hiba-from-counters verdict.", "dup_of": null, "unique_facts": "Needs an injection harness (new mechanism) or a real failing disk; cannot close from source.", "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-333", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "(b) felhom-agent/internal/storage/hostops.go:374: `out, stderr, err := h.runner.Run(ctx, h.bins.Smartctl, \"-a\", \"-j\", device)` — no -n standby. (a) still an operator decision (Viktor decides).", "dup_of": null, "unique_facts": "Not in the row: (b) is not a one-line agent change — configs/felhom-agent.sudoers:34 pins the exact argv `/usr/sbin/smartctl ^-a -j /dev/(...)$`, so adding `-n standby` also needs a sudoers change delivered by the signed bundle.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-338", "sev": "P3", "category": "Security & access", "group": "UNCHECKED", "evidence": "felhom.eu/documentation/operations/nodes.md:86-88 now says demo-hp 'Agent config shape (R-50 island): local_api on 169.254.253.1:8443/vmbr9, guest eth1 169.254.253.2/30', and :84 records demo-hp was reprovisioned (address 192.168.0.87 -> 192.168.0.104, read 2026-09-21). Whether the reprovisioned box is actually on the island is a live-box fact (agent.json, pct config) not provable from source.", "dup_of": null, "unique_facts": "A reprovision since the row was filed (fresh installs are born-on-island per memory) may have made nodes.md true; a read-only check of demo-hp's agent.json listen_addr settles it.", "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-340", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/scripts/felhom-tenantsync.sh: no health op and no 8007 probe (grep '8007\\|health)' empty). Needs an ep0 (protected) script version bump + hub signal (M).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-349", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-agent/internal/hub/report.go:282-292 reports only WrapperSHA256; no agent binary sha field in the report (grep AgentSHA256 finds only the hub's manifest entry, hub/internal/api/handler.go:2726). R-349 commits 40d857b/910fd911 are the manual correction only.", "dup_of": null, "unique_facts": "Fix spans agent (report own sha) + hub (compare to vouched agent_sha256).", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-350", "sev": "P3", "category": "Security & access", "group": "UNCHECKED", "evidence": "Whether the hub operator password was rotated after 2026-08-20 lives only in the hub DB / operator; git log shows no rotation record (only 910fd911 filing it). Not determinable from source.", "dup_of": null, "unique_facts": "The reusable lesson is already in memory (curl-w-redirect-url-leaks-credentials). If the operator confirms no rotation and accepts the risk (value is in a local transcript on DooPlex only), this can close as accepted; rotation itself costs ~5 minutes via /configuration.", "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-362", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/internal/backup/offbox_restore.go:380, offbox.go:1929, shares_restore.go:116: `return fmt.Errorf(\"restore dir: %w\", err)` — no drive-state consultation on the restore path; IsDisconnected (settings.go:2001) is consulted only by backup legs/update guard (backup.go:609, tier2.go:457, …). No R-362 commit.", "dup_of": null, "unique_facts": "The settings IsDisconnected flag may lag a detach by seconds (the observed detach was 4 s into the restore), so the check should also test the drive mountpoint directly, not only the flag.", "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/backup/offbox_restore.go", "controller/internal/backup/offbox_restore_test.go"], "change": "When MkdirAll/write of the restore destination fails with EACCES/ENOENT, check whether the destination's drive is disconnected/decommissioned or no longer a mountpoint, and return a Hungarian error naming the drive instead of the raw permission error.", "test": "Unit test with a temp dir made read-only plus a settings stub marking the drive disconnected -> error names the drive, not 'permission denied'; red on current code.", "minutes": 60}, "not_worth": null, "minutes_spent": 6}
{"id": "R-363", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-SMALL", "evidence": "felhom-controller/controller/cmd/controller/main.go:1546: `sched.Daily(\"fill-watch\", \"03:30\", func(ctx context.Context) error { return fillWatcher.Check() })`. Watcher emits on escalation only with a persisted band (internal/fillwatch/fillwatch.go:133, :231), so a faster cadence does not spam.", "dup_of": null, "unique_facts": "Recommendation not followed: sharing the reserve's reading is a design change; an hourly sched.Every gives the same outcome cheaply because Check() is escalation-only and persisted.", "small_fix": {"repo": "felhom-controller", "files": ["controller/cmd/controller/main.go (gitignored dir — git add -f)", "a source-pin test"], "change": "Replace sched.Daily(\"fill-watch\", \"03:30\", …) with sched.Every(\"fill-watch\", time.Hour, …) (scheduler.go:104), keep the startup check.", "test": "fillwatch test: two consecutive Check() calls at the same band emit once (proves hourly is safe); a source-pin test that main.go registers fill-watch via Every.", "minutes": 30}, "not_worth": null, "minutes_spent": 6}
{"id": "R-388", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Product direction, operator's call; recorded as [DESIGN — DIRECTION] in documentation/architecture/08-alarm-ladder.md §8 per the row. Nothing in source to fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-401", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Event-triggered watch row: felhom-controller/controller/internal/backup/offbox_integrity.go:63 `const integrityCheckTimeout = 30 * time.Minute`, :78 `var integritySlowNoticeThreshold = 5 * time.Minute` — unchanged; the trigger (slow WARN on a large store) has not been recorded.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-409", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller/controller/internal/backup/recovery_unit.go:58 `Checksums map[string]string json:\"checksums\" // sha256 of captured compose/ files`; writers at :147, :152, :202 hash only compose/.felhom.yml/app.yaml — no db_dumps or volume_dumps hash.", "dup_of": null, "unique_facts": "Changes the recovery-unit manifest format (capture + verify side); not under an hour.", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-412", "sev": "P3", "category": "Backup & restore", "group": "NOT-WORTH-IT", "evidence": "Leg 1 shipped: felhom-controller fcef8e0 / 62c6a8a (v0.232.0, R-412a) — hollow push now logs at WARN, pinned by TestR412a_EmptyPushDoesNotReadAsAPlainSuccess. Leg 2 (re-read the unit before push vs accept the race) has no code and is a decision.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A narrow race: if a recovery unit is destroyed inside an off-site run after its own dump leg, the push ships the just-rebuilt hollow unit; the next run repairs it and the WARN line now says it carried no data.", "cost": "A re-read/re-validate step in the push path plus a red-proof drill; touches the capture/push boundary the architecture says not to guard (08 §8.2).", "if_never": "Rarely, one off-site snapshot of one app is hollow until the next nightly run; the R-403 mirror guard keeps the good secondary copy; the WARN makes it visible.", "pick": "close-as-accepted"}, "minutes_spent": 4}
{"id": "R-433", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Hetzner answered (ticket per the row): file-level snapshot access is a MAIN-account capability. hub/internal/hetznerapi/hetznerapi.go still has no snapshot read method (only size_snapshots usage, :83-87). The owed proof (read a file from a snapshot with the main account; forced-command append-only key) is a live, credential-bound operator act.", "dup_of": null, "unique_facts": "The row's state cell 'BLOCKED-ON-PROVIDER' is stale — the provider has answered; the row should be restated as 'owed: one main-account measurement' (operator + CC).", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-435", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom.eu/hub/internal/monitor/offsite.go:255: `snapshotDropFraction = 0.5 // more than half the history gone in one step` — no per-tag second signal exists.", "dup_of": null, "unique_facts": "Not in the row: STATUS.md no longer contains a 'noticed within a day' claim (grep -i 'within a day|deletion|half' empty), so that half of the row's reason for staying open is gone; what remains is the per-tag detector (new mechanism).", "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-440", "sev": "P3", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog-felhom.eu @917a779: 15 templates still have no `update_ladder:` in .felhom.yml — bentopdf code-server glance gokapi gramps-web homebox homepage jellyfin onlyoffice plant-it plex recipe-importer seerr vaultwarden wanderer (calibre-web got its first step in 53a4a1d). For these, pins float with no recorded digest.", "dup_of": null, "unique_facts": "Closes per app as R-462 (widen the upgrade harness) proves a first ladder step; the pre-v0.269.0 installs heal on their next guarded update.", "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-444", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No fstrim anywhere in felhom-agent or felhom.eu/scripts (grep 'fstrim' over *.go/*.sh empty). Needs a new periodic host job (agent, privileged, sudoers/bundle) and possibly an operator surface.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-446", "sev": "P3", "category": "App updates", "group": "DUPLICATE", "evidence": "felhom-controller/controller/internal/stacks/updateorder.go:96: `if len(s.CatalogDigests) == 0 || s.CatalogTestedAt.IsZero() { return false }` — blind only for apps with no ladder entry, i.e. the same 15 templates R-440 lists (app-catalog has no update_ladder for them). Both rows close by the same act: each app's first proven ladder step (R-462).", "dup_of": "R-440", "unique_facts": "Move into R-440: the customer-visible consequence — the 'Naprakész' badge cannot go 'behind' for those 15 apps (updateorder.go:96); R-740 (same-tag re-test) is CLOSED (catalog 6a3ead9), so floating-tag drift for laddered apps is handled.", "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-450", "sev": "P3", "category": "App updates", "group": "FIXED-BY-LATER-WORK", "evidence": "The row's only remainder was 'the other ten PostgreSQL apps need two-venue proof'. R-463 CLOSED 2026-09-30 by felhom.eu 25cb3eb9 ('The last six PostgreSQL apps decided'): 8 of 11 moved by the box's own conversion, 3 (zipline, adventurelog, immich) stay by decision 42; CLOSED-ITEMS.md:223. Source proof: app-catalog templates/docmost/docker-compose.yml:62 'image: postgres:18-alpine' (also rallly:67, outline:64, paperless-ngx:101). The per-app engine gate stays as the permanent rule (catalog CLAUDE.md:115-122).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-458", "sev": "P3", "category": "App updates", "group": "NOT-WORTH-IT", "evidence": "Still true by design: Syncer.copyTemplates copies .felhom.yml verbatim. The row narrows the risk to type: api probes with expect; those exist (e.g. templates/dawarich/.felhom.yml:104 'expect:', adventurelog:93, immich:116, nextcloud:108). Quick catalog history scan (commits touching .felhom.yml health/expect lines AND an image: line) found only new-app commits (96829d0, 72247a3, 882ac14, 195129c, 4351d08) plus lifecycle/re-pin commits a325416/b3eabfd; b3eabfd's wanderer diff showed no healthcheck lines. So no evidence of a healthcheck changed together with a version move on an existing app.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A frozen (pinned-behind) app can receive a newer health check from .felhom.yml. The only result is a false 'degraded'/dead-app alarm, never data loss, and only for type: api probes with an expect block.", "cost": "Freezing just the healthcheck key means the sync must parse and rebuild a metadata file on the path that touches every app every 15 minutes. That is new surface on a hot path.", "if_never": "Possibly a false alarm one day for an app pinned behind, if a catalog commit changes an expect-probe together with a version. History shows none so far, and the ladder now moves apps step by step.", "pick": "close-as-accepted"}, "minutes_spent": 12}
{"id": "R-462", "sev": "P3", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Ongoing multi-session work (fixtures and ladders per app). Last progress: catalog e6f3ec2 (2026-09-30), audits/more-night-apps-2026-09-30/. 21 apps still have no ladder (per the row). Not checkable as done from source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-468", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "A standing pre-customer arrangement, not a defect. The mechanism is live: felhom.eu/scripts/golden_currency_gate.py:137 reads documentation/tests/golden-waiver.yml. The waiver file is currently ABSENT: deleted in felhom.eu 5efe6dae (2026-10-04, 'golden 0.292.0 vouched ... golden waiver deleted'). The row retires only at the first external install, so it stays as a watch.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-469", "sev": "P3", "category": "App updates", "group": "STILL-TRUE-SMALL", "evidence": "What stood between this row and its close was R-463, now CLOSED (felhom.eu 25cb3eb9; CLOSED-ITEMS.md:223). The per-app PostgreSQL gate (decision 35) is now the permanent rule. One thing still contradicts source: app-catalog-felhom.eu/CLAUDE.md:103 heading reads '- **A MariaDB major gets its OWN EDGE; PostgreSQL and MySQL may not cross a major at all.**', but the body at :115-122 lets PostgreSQL cross per app with engine_conversion + two-venue proof, and 8 apps already did (e.g. templates/docmost/docker-compose.yml:62 'image: postgres:18-alpine').", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["CLAUDE.md"], "change": "Reword the rule heading at CLAUDE.md:103 and the 'What is NOT lifted' paragraph (:112-114) to say that PostgreSQL crosses a major one app at a time with engine_conversion + both-venue proof (decision 35) and that MySQL stays refused. Then close R-469 citing R-463's closure. Do not delete the gate: it is now the per-app enforcement.", "test": "Run python3 scripts/catalog_gates.py (unchanged result expected). grep CLAUDE.md for 'may not cross a major at all' -> 0 hits.", "minutes": 20}, "not_worth": null, "minutes_spent": 8}
{"id": "R-489", "sev": "P3", "category": "Apps & catalog", "group": "FIXED-BY-LATER-WORK", "evidence": "The residual (a unit-restore-recreated volume has no compose label, so the remove answered []) was fixed in felhom-controller 206b035 (v0.268.0, R-658). controller/internal/stacks/delete.go:1089-1090: '// appVolumeSet is every volume the removal accounts for: the ones carrying the project label AND the // ones the app's definition declares that Docker holds by name (R-658, v0.268.0).' delete.go:703 'resp.VolumesRemoved = removedVolumes(volsBefore, m.appVolumeSet(name, stackDir))'.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-498", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "Still literal: grep -l '\\.DOMAIN' over app-catalog templates/*/.felhom.yml -> 58 of 58; e.g. templates/bookstack/.felhom.yml:73 \"- 'Nyisd meg a wiki.DOMAIN címet a böngészőben'\". The controller renders first_steps as-is: controller/internal/web/templates/app_info.html:224 '{{range .AppInfo.FirstSteps}}<li>{{.}}</li>{{end}}' (also deploy.html:684). The precedent substitution exists for default creds only: controller/internal/web/known_login.go:67 'strings.ReplaceAll(meta.AppInfo.DefaultCreds, \"DOMAIN\", s.cfg.Customer.Domain)'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/handlers.go (app info view model)", "controller/internal/web/known_login.go (pattern)", "new test in controller/internal/web/"], "change": "When building the app info view, rewrite each first_steps entry: replace '<word>.DOMAIN' with the stack's real address (installed SUBDOMAIN + customer domain when installed, else the template default subdomain + customer domain). Use the same approach as known_login.go:67.", "test": "A render test over every catalog template's first_steps (or a fixture of 3) asserting that no rendered string contains the literal 'DOMAIN'. A red-proof with the substitution removed must fail.", "minutes": 60}, "not_worth": null, "minutes_spent": 8}
{"id": "R-516", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-NOT-SMALL", "evidence": "By its own text it now waits for a Hungarian walk on a box with a second drive (items 4, 7, 8, 9, 10) and needs a separate row for item 11. That is live-box work, not source. No commit after 2026-09-20 names R-516 as closed.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-521", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No storage-disconnect suppression of app_start_failed: controller/cmd/controller/main.go:2796 'down := (stacks.IsDownState(st.State) || crashLooping) && !userStopped && !quiesced[st.Name]' (only user-stop/quiesce suppress), and controller/internal/notify/notifier.go:718 emits app_start_failed per newly-down app. It also needs the hub cooldown semantics changed (per-key cooldown outliving storage_reconnected) and an operator decision on customer mail policy. That is two repos plus a decision.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-522", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No tunnel-connection signal in the controller: grep for TunnelConnected/cloudflared connection state in controller/internal -> none. The tile comes from container metadata: controller/internal/web/inframeta.go:21-22 '\"cloudflared\": { DisplayName: \"Cloudflare Tunnel\"'. Fixing it needs a new state source (cloudflared metrics or the hub-push result) wired into the tile, plus a live internet-cut validation.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-531", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Both measurements are done (audits/evidence-drill-0243-2026-09-16/). What remains is an operator design question about the crash-loop budget (a slow loop every 20 min is never paused). That is an operator decision, not code.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-540", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still a single pool box: felhom.eu/hub/cmd/hub/main.go:367 'poolBoxID, _ := strconv.ParseInt(os.Getenv(\"HETZNER_POOL_BOX_ID\"), 10, 64)'. It needs a selection-rule design and eventually a second box (money). No risk today (0.3 % full per the row).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-542", "sev": "P3", "category": "Storage & devices", "group": "UNCHECKED", "evidence": "The controller passes the agent's 'initialize' list through untouched: controller/internal/web/agent_disk_handlers.go:160-162 mergeAttachCandidates only appends to Attach. The agent puts every candidate under initialize: felhom-agent internal/localapi/disks.go:438 'initialize = append(initialize, c) // every unclaimed disk can be initialized'. Whether a REGISTERED in-guest drive still counts as 'unclaimed' depends on the agent's ListCandidateDisks claim state on a real box (internal/storage/candidates.go). Only a live box shows that. No commit names R-542.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 8}
{"id": "R-545", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still no un-configure route: controller/internal/web/server.go:744-783 lists /backup/offbox/{config,toggle,enable-all,offer-dismiss,run,reset,status,restore,place,reconstitute,verify-copy/delete,confirm-escrow,inject-password}; nothing removes a target. The fix is a new destructive customer action (shred data/offbox/) with a hub-escrow refusal check (R-241 rule), Hungarian/English copy and a UI. That is more than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-547", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-SMALL", "evidence": "Still daily: controller/cmd/controller/main.go:1546 'sched.Daily(\"fill-watch\", \"03:30\", func(ctx context.Context) error { return fillWatcher.Check() })' plus one startup check (:1557-1566). The check is edge-triggered against PERSISTED state (comment at :1553-1555), so running it more often adds no repeat mails.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/cmd/controller/main.go", "felhom.eu/documentation/architecture/08-alarm-ladder.md (one sentence, optional)"], "change": "Also schedule the fill-watch on an interval (e.g. sched.Every(\"fill-watch-fast\", 15*time.Minute, ...) calling the same fillWatcher.Check) next to the daily run, and log the cadence. Alternative if the operator prefers: state in 08-alarm-ladder.md that a transient full disk is out of scope.", "test": "Unit test with a fake usage func: a target that crosses 95 % between two interval ticks yields exactly one notification, and a second tick at the same level yields none (pins edge-triggering under the faster cadence).", "minutes": 50}, "not_worth": null, "minutes_spent": 8}
{"id": "R-548", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "The row's original fix shape SHIPPED: felhom-agent 0722b2c (2026-09-24, R-685) checks before start, internal/backup/runner.go:279 '... so a new one needs about %s (old archives are removed only after a successful backup)'. Shown in the UI by controller 44ae4de (v0.272.0). The 2026-09-30 addendum is still true and is the open part: the local tier is refused for ever (10 refusals on demo-hp) because the old archive is removed only after a success. Nothing gives the room back. That needs a design (prune before write, or move the tier), so it is not small. Consider re-scoping the row to that residual.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-552", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "The interrupted-restore notice is cleared only at controller/internal/backup/opstatus.go:61 'delete(m.opInterrupted, stack)' inside BeginRestoreOp. removeStack (controller/internal/api/router.go:955) clears only the update hold, at router.go:1054 'if cleared, err := r.sett.ClearUpdateHold(name); err != nil {'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/backup/opstatus.go", "controller/internal/api/router.go", "tests in internal/backup and internal/api"], "change": "Add Manager.ClearInterruptedRestore(stack) (delete from opInterrupted + persistRestoreRecordLocked under m.mu). Call it in removeStack next to ClearUpdateHold, and log when it cleared something.", "test": "Unit test: record an interrupted restore for app X, call ClearInterruptedRestore(X), and assert that the list is empty and the persisted record no longer holds X. Wiring test: removeStack on a router with a fake backup manager calls the clear. Red-proof by removing the call.", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-554", "sev": "P3", "category": "Install & onboarding", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still present: controller/internal/setup/ (setup.go, handlers.go, csrf.go, network.go, templates/), and controller/cmd/controller/main.go:328-330 'if setup.NeedsSetup(cfg) { ... runSetupMode(cfg, logger)'. The fix deletes a package and adds a new waiting page (HU+EN copy) with a red-proof. It also needs a check of drill/golden reliance on .needs-setup (controller/internal/web/handler_debug.go references it). More than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-562", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Needs an operator word on the Hungarian number/date format (the row says so) and a deliberate Hungarian-byte change release with parity re-capture. Not checked further.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-565", "sev": "P3", "category": "Process & tooling", "group": "STILL-TRUE-SMALL", "evidence": "The English page test detects only accented letters: controller/internal/web/i18n_parity_test.go:563 'func huLetter(s string) bool {' used by TestI18nEnglishPages (:601). The ASCII Hungarian list exists only in the extractor: controller/scripts/i18n_extract.py:40 'ASCII_HU = re.compile(r\"\\b(Fut|Nincs|Igen|Nem|Hiba|Mentve|...'. No Go test uses it.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/i18n_parity_test.go"], "change": "Add an ASCII Hungarian word regex to TestI18nEnglishPages (seeded from i18n_extract.py ASCII_HU plus the words releases B/C found: mp, db, FIGYELEM, jelenlegi, majd a(z), Konfig, Megtartva, helyi, Befejezve, automatikus, kedd/szerda/szombat, szint), applied after the data mask.", "test": "Negative control: a plain English sentence passes. Decoy: plant 'mp' in an English value and the detector must flag it. Then run the existing English renders (go test ./internal/web -run TestI18nEnglishPages, no Docker).", "minutes": 55}, "not_worth": null, "minutes_spent": 6}
{"id": "R-573", "sev": "P3", "category": "Monitoring & notifications", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom-controller 7c4a33b (v0.258.0, 'the last four Hungarian things an English household met ... R-573 the two channel banners'). controller/internal/web/alerts.go:113 'func (am *AlertManager) SetAgentChannelAlert(down bool, msgKey, msg string) {' and :136 'func (am *AlertManager) SetEndpointDriftAlert(drift bool, msgKey, msg string) {'. Both set MessageKey with msg only as a fail-open fallback.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-575", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "Still a plain string: controller/internal/stacks/deploy.go:1456 'func (m *Manager) memoryVerdict(newReqMB, newLimitMB, releasedReqMB, releasedLimitMB int) (refusal error, warning string) {'. Callers are deploy.go:302 and update.go:497. The consequence is stated at controller/internal/stacks/deploy_errors.go:41.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/stacks/deploy.go", "controller/internal/stacks/update.go", "the deploy response renderer in internal/web or internal/api"], "change": "Make memoryVerdict return (refusal error, warningKey string, warningArgs []any) instead of a msgHU string, and render the warning at the deploy answer with the request's language (the Alert/UpdateRefusal pattern).", "test": "Test that a deploy over the soft memory line returns the English warning for lang=en and the unchanged Hungarian bytes for hu (parity fixture). Red-proof: forcing hu rendering fails the en case.", "minutes": 60}, "not_worth": null, "minutes_spent": 5}
{"id": "R-578", "sev": "P3", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "The lock-reentrancy guard is still one test in one package: controller/internal/backup/offsite_diag_test.go:190 'TestNoteHelpersAreNotCalledUnderTheSettingsLock'. No other package has one, and there is no gate (grep for R-578 in controller -> none). The fix needs a cross-package AST gate that knows which methods read settings (or a re-entrant read path in Settings). That is a new mechanism, likely more than an hour with decoys.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-581", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "Still no tie-break: felhom.eu/hub/internal/store/store.go:1329 'SELECT customer_id, MAX(received_at) as max_time' joined on 'r.received_at = latest.max_time' (:1333). A same-second tie returns BOTH rows (a duplicate customer in GetCustomers), not just an arbitrary one. Other newest-report queries also order on received_at alone: store.go:1393 and :1446 'ORDER BY received_at DESC'. Compare :3574, which already has ', id DESC'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/internal/store/store.go", "hub/internal/store/*_test.go"], "change": "In GetCustomers, join on MAX(id) per customer_id instead of MAX(received_at), and add ', id DESC' to the ORDER BY at store.go:1393 and :1446.", "test": "Store test: write two reports for one customer in the same tick with different health/version, then assert that GetCustomers returns exactly one row for that customer and that it carries the second report. Red-proof against the current query (expect 2 rows or the wrong one).", "minutes": 40}, "not_worth": null, "minutes_spent": 6}
{"id": "R-584", "sev": "P3", "category": "Security & access", "group": "NOT-WORTH-IT", "evidence": "Still true as a process gap: the only written rule is felhom-controller/.claude/rules/ui-hungarian.md (credential-bearing helper cleanup). grep for 'shred' over the workspace .claude/rules and felhom.eu/skills -> no hits, so there is no 'last act of a phase' mechanism. The five files were already shredded (per the row).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Helper scripts with the shared DEMO controller password inline were left in a demo guest's /tmp. The cleanup rule exists, but no mechanism enforces it.", "cost": "A real mechanism would be a push-helper wrapper that writes credentials to a 0600 file and shreds on exit, and every session would have to use it. Another rule line would be a wish, as the row itself says.", "if_never": "Demo-box (Tier 0) password litter may recur on throwaway guests. The password is a shared demo one, not a customer one, and rotating it is cheap.", "pick": "close-as-accepted"}, "minutes_spent": 5}
{"id": "R-585", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still finished Hungarian from callers: controller/internal/notify/notifier.go:408 'n.PushEvent(\"backup_failed\", \"error\", message, BackupDetails{Error: errMsg})', :487 offbox_enlarge_blocked, :497 db_dump_failed. None of the six types is in convertedProducers (controller/internal/notify/message_customer_test.go:39). The hub still has no customerMessages entry for offbox_enlarge_blocked (felhom.eu/hub/internal/notify/templates.go:94/153). The fix changes six producers' callers across packages: more than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-586", "sev": "P3", "category": "Process & tooling", "group": "NOT-WORTH-IT", "evidence": "The harness is still in no push-time gate or CI: grep for 'bootstrap-modes' over felhom.eu/scripts and .gitea -> none. It is required only in documentation/runbooks/iso-release-gate.md:285 'docker run --rm -v <repo>/scripts/iso:/work felhom-iso-assistant:trixie bash /work/test/bootstrap-modes.sh'. The FIFO fix shipped per the row.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The ISO bootstrap harness runs only at each ISO release gate (G16), not on every push.", "cost": "CI needs a container-capable job (a new runner capability), or the push hook needs Docker. Both are new infrastructure for a harness that matters only when an ISO is released.", "if_never": "A harness break is caught at the next ISO release instead of at the push. ISO releases are infrequent, and that gate already blocks a broken one.", "pick": "close-as-accepted"}, "minutes_spent": 6}
{"id": "R-587", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "The files are gone (ls /mnt/5_hdd/felhom.eu/felhom-iso/out | grep -c rootpw -> 0). The guard is still not built: the publish still relies on the include pattern, felhom.eu/skills/felhom-build-deploy/SKILL.md:115 'rclone/rclone:latest copy /data R2:felhom-iso --include \"felhom-installer-<VER>*\"'. The build still emits the file for appliance mode: felhom.eu/scripts/iso/build-felhom-iso.sh:351 '... > \"$OUT_ISO.rootpw.txt\" )'.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu", "files": ["scripts/iso/build-felhom-iso.sh", "skills/felhom-build-deploy/SKILL.md (publish step)", "scripts/iso/test/ (a small test)"], "change": "At the start of build-felhom-iso.sh, refuse (non-zero exit, named reason) when any *.rootpw.txt exists in the output dir. In the SKILL publish block, add a pre-check line that aborts the rclone copy if a *.rootpw.txt is present in the publish source.", "test": "Shell test: a temp out dir with a decoy x.rootpw.txt makes the build entry refuse with exit != 0. The same dir without it proceeds past the check (stub the rest with an early-exit env flag).", "minutes": 45}, "not_worth": null, "minutes_spent": 6}
{"id": "R-593", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-SMALL", "evidence": "Still wrong: app-catalog-felhom.eu/templates/papra/.felhom.yml:47 ' description: \"Az alkalmazás aldomainje\"' sits under AUTH_SECRET (:37). SUBDOMAIN (:30-35) has no description.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "app-catalog-felhom.eu", "files": ["templates/papra/.felhom.yml", "scripts/copy_freeze/hu.json"], "change": "Move 'Az alkalmazás aldomainje' to SUBDOMAIN, give AUTH_SECRET its own description (e.g. 'A munkamenetek aláírásához használt kulcs — ne generáld újra'), add the two English i18n.en descriptions, and re-capture papra's entries in copy_freeze/hu.json with the reason in the commit.", "test": "python3 scripts/catalog_gates.py green (copy-freeze and i18n coverage). Catalog English ceiling for papra reaches 14/14.", "minutes": 25}, "not_worth": null, "minutes_spent": 5}
{"id": "R-600", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "Log line unchanged: felhom.eu/hub/internal/web/customer_delete.go:310 's.logger.Printf(\"[INFO] customer DELETE cascade COMPLETE for %s (journal #%d) — full teardown\", customerID, journalID)'. The wgsync Trigger is called only from hub/internal/api/wg.go:86 'h.wgSyncer.Trigger()', not from the customer cascade. Measured lag is seconds to about 6 min (row).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": ["hub/internal/web/customer_delete.go", "hub/cmd/hub/main.go (wire the reconciler into the web server if not already)", "test in hub/internal/web"], "change": "Call the wgsync reconciler's Trigger() before the COMPLETE log line (nil-safe), and make the line say 'wg peer removal pushed' or 'queued for the next wgsync push' depending on whether a syncer is wired.", "test": "Web test with a fake reconciler: a completed delete cascade calls Trigger exactly once, and the log line contains the peer-removal state. Red-proof by removing the call.", "minutes": 50}, "not_worth": null, "minutes_spent": 6}
{"id": "R-607", "sev": "P3", "category": "App updates", "group": "UNCHECKED", "evidence": "The row asks first for a live reproduction loop (push a tag, sync, read catalog_images on a timer) and has no diagnosis. Why the sync reports 'nincs változás' while the cache moved, and when CatalogImages refreshes, are live-box behaviour I could not settle from source in the time box. No commit after d19f07ea (filing) names R-607 as fixed.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-612", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "The memory half is fixed (catalog a5a729a, 'wishlist 512M (R-612)'). The open half, making a failed first-boot seed visible, needs a new detection mechanism (read the seed's exit/log, or a probe that checks the Role/Group rows). No commit addresses it.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-613", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "uptime-kuma is fixed (catalog a5a729a). The open half is a sweep of all 58 templates for probes that pass on a setup wizard. That needs per-app live inspection, so it is not small. No commit names it.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-615", "sev": "P3", "category": "App updates", "group": "STILL-TRUE-SMALL", "evidence": "Still inert: controller/internal/sync/sync.go:279 clones only 'if _, err := os.Stat(gitDir); os.IsNotExist(err)'. Later cycles run :299 'fetch --depth 1 origin' against the stored remote, and nothing compares cfg.Git.RepoURL with origin (grep 'set-url|remote' in sync.go -> none).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/sync/sync.go", "controller/internal/sync/sync_test.go"], "change": "Before the fetch, run 'git remote set-url origin <buildRepoURL()>' (or read origin and re-clone on mismatch), and log at INFO, masked, when the remote changed. The appended drill-folder residue (stack folders the live catalog lacks) is a separate drill-teardown item and is not part of this fix.", "test": "Test with two local bare repos (git only, no Docker): clone A, switch cfg.Git.RepoURL to B, sync, and assert that the cache HEAD is B's commit. Red-proof against current code.", "minutes": 50}, "not_worth": null, "minutes_spent": 7}
{"id": "R-616", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-SMALL", "evidence": "Still true: controller/internal/sync/sync.go:327-331 buildRepoURL injects 'https://%s:%s@' and the clone at :283-288 passes that URL to 'git clone', so it persists as origin. maskRepoURL (:100) masks only log lines. Inert on the fleet while git.token is empty (row).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/sync/sync.go", "controller/internal/sync/sync_test.go"], "change": "Clone and fetch with the credential-free RepoURL, and supply credentials per command via '-c http.extraHeader=Authorization: Basic <b64>' (or GIT_ASKPASS env) only when username+token are set. Pairs naturally with the R-615 set-url fix (set-url to the bare URL).", "test": "Test with a local repo and a token configured: after clone and after a sync, 'git -C cache config remote.origin.url' contains no '@'. Red-proof on current code. The operator token rotation stays a separate operator act.", "minutes": 55}, "not_worth": null, "minutes_spent": 5}
{"id": "R-622", "sev": "P3", "category": "App updates", "group": "FIXED-BY-LATER-WORK", "evidence": "adventurelog v0.13.0 was diagnosed, fixed and promoted with a two-venue test record in app-catalog-felhom.eu 06ea7da (2026-09-27, 'adventurelog: v0.12.1 -> v0.13.0 with its health and world-data fixes in the same commit (R-655, 09 decision 41)'; bench healthy in 217 s, box 9202 through the guarded Update in 204 s). templates/adventurelog/docker-compose.yml:13 ' image: ghcr.io/seanmorley15/adventurelog-backend:v0.13.0'. The proven step is recorded at templates/adventurelog/.felhom.yml:132 (update_ladder entry from v0.12.1).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-635", "sev": "P3", "category": "Monitoring & notifications", "group": "FIXED-BY-LATER-WORK", "evidence": "The open remainder (app_oom fires once per container run, no escalation) was built in felhom-controller 0054d4b (v0.265.0, 'OOM storm alarm', R-636). controller/internal/notify/notifier.go:746 '\\t\\tn.emit(\"app_oom_storm\", \"error\",' fires once per run when 20 or more kills land in 30 min (:754-764, oomStormKills=20, oomStormWindowMin=30; pinned by TestR636_*). The 79 % headroom and the method lesson are carried by R-462 (per the row).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-645", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still true for the operator CLI: controller/internal/settings/settings.go:1923-1929 ClearRestoreHold deletes ANY hold reason (including update-failed) with no pin restore, and the flag is in controller/cmd/controller/main.go:94/198. The row lists three candidate shapes with 'none chosen'. Picking one changes operator-path semantics and the capture logic, so it is not a one-hour fix. (The automatic undo is no longer affected, since v0.263.0, per the row.)", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 6}
{"id": "R-675", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-SMALL", "evidence": "Unchanged: controller/internal/web/handlers.go:1745 'return head + \"A fájlok a második meghajtó másolatából állíthatók vissza: „Fájlok visszaállítása”.\"'. The branch does not check for a whole copy on the second drive (decision 26, v0.269.0).", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom-controller", "files": ["controller/internal/web/handlers.go", "a handler test in controller/internal/web"], "change": "In missingFileLegsRefusal, when the second drive holds a whole copy of the app (the decision-26 whole-copy check the backup manager already exposes for the restore page), name that whole restore action instead of 'Fájlok visszaállítása'. Keep the existing branch otherwise.", "test": "Three-branch test with a fake backup manager (off-site row / whole copy on drive 2 / tier-2 files only / nothing), asserting each sentence. Red-proof: the whole-copy case fails on current code.", "minutes": 45}, "not_worth": null, "minutes_spent": 5}
{"id": "R-676", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "A watch row, still true: controller/internal/stacks/unhealthy.go:116 skips only 'if st.Deploying || st.Updating || st.HoldReason != \"\" || st.updateHeld {', so a deploy's first start (after Deploying clears) is sampled by decision 28's crash-loop stop. The immich cause is fixed in the catalog (56c4888, 768M, per the row). Covering a slow first start would need a first-start grace decision.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 5}
{"id": "R-682", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "felhom-controller 7690c27 (v0.296.0): no remove journal in source — grep -rni 'remove.*journal|removeJournal|remove_intent|interrupted remove' controller/*.go returns nothing; git log --grep R-682 empty. Needs a new boot-time journal mechanism.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-683", "sev": "P3", "category": "App updates", "group": "UNCHECKED", "evidence": "Watch item about a power-cut drill outcome; behaviour only a live box shows; git log --grep R-683 empty in controller.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-698", "sev": "P3", "category": "Backup & restore", "group": "NOT-WORTH-IT", "evidence": "Row is an open operator decision among options (a)-(d); no source change implied. No commit references R-698 fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "A backup records the image name/digest, not the image; restoring a version the maker deleted from the registry fails at the pull.", "cost": "Options b-d add a new mirror or tens-hundreds of MB per app per copy on every tier, a new part on the recovery path.", "if_never": "A restore of a deleted version fails; the household uses the next copy or a newer version (option a). All 42 ladder digests resolved when measured.", "pick": "close-as-accepted (option a), operator to confirm"}, "minutes_spent": 2}
{"id": "R-700", "sev": "P3", "category": "Storage & devices", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom-controller 820e8ef (v0.276.0, R-697/R-700). controller/internal/stacks/migrate.go:789: 'm.logger.Printf(\"[INFO] [stacks] %s: data moved %s -> %s — app.yaml keeps its pin (%d service(s)) and records\"' — persistDriveFlip (migrate.go:763) loads app.yaml and changes only HDD_PATH. Only a live proof on a two-drive box remains (row's own residue).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-704", "sev": "P3", "category": "Apps & catalog", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom-controller 7cba0bf (v0.278.0). controller/internal/api/router.go:918 'func (r *Router) dropLeftoverHold(name, why string) {' calling r.sett.ClearUpdateHold(name); pinned by TestR704_AFreshInstallDropsALeftoverHold. Residue: live proof of the install-time drop only.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-706", "sev": "P3", "category": "Backup & restore", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom-controller 0c702f8 (v0.279.0). controller/internal/api/router.go:946 'if err := r.backupMgr.DeleteOffsiteRestoreCopy(name); err != nil {' inside removeVerificationCopy (R-706); pinned by TestR706_RemovalWithBackupsDeletesTheVerificationCopy. Residue: not seen live.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-717", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "app-catalog templates/opengist/.felhom.yml:42 and templates/wishlist/.felhom.yml:42 carry only signup_block; no after_setup/after_install in either .felhom.yml (grep empty). Fix needs per-app DB writes (opengist sqlite with app stopped) plus live proof.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-723", "sev": "P3", "category": "Monitoring & notifications", "group": "FIXED-BY-LATER-WORK", "evidence": "felhom.eu 80aeac71 (hub v0.126.0). hub/internal/monitor/staleness.go:174 '// R-723 (v0.126.0): a customer's NEW box is not a recovery.'; hub/internal/notify/dispatcher.go:540 '\"suppressed\", \"first hour of a new box (R-723)\", \"operator\"'. Residue: live proof at a real first install.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-724", "sev": "P3", "category": "Monitoring & notifications", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Text parts fixed in controller 6be6c53 (v0.283.0). Remaining LAN/gateway read still goes only through the samba container: controller/internal/stacks/guestnet.go:47 'out, err := dockerexec.Command(\"docker\", append([]string{\"exec\", sambaContainer}, args...)...).Output()' — the comment (guestnet.go:18) accepts that reads fail while sharing is off. Needs another read path (design).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-728", "sev": "P3", "category": "Hub & operator", "group": "STILL-TRUE-SMALL", "evidence": "No commit fixes R-728 (git log --grep in felhom.eu only the filing commit 11591f3a). hub/internal/web/configs.go:705 'existing, _ := s.store.GetCustomerConfig(customerID)' is a check-then-act before slow applyOffsite/applyPBSDR; store.go:1592 save is an upsert ('retrieval_password = excluded.retrieval_password'), so two concurrent submits both pass and both mint.", "dup_of": null, "unique_facts": null, "small_fix": {"repo": "felhom.eu (hub)", "files": "hub/internal/web/configs.go (+ a new _test.go)", "change": "Add a per-customerID in-flight guard (sync.Map/mutex set) around the create handler from the duplicate check to the self-bind mint, so a second concurrent submit for the same ID gets the 'already exists' form; optionally disable the submit button on submit in the template.", "test": "Unit test firing two concurrent POSTs for the same customer ID with a blocking applyOffsite seam; assert exactly one 'Customer config created' / one self-bind mint; red-proof by removing the guard.", "minutes": 60}, "not_worth": null, "minutes_spent": 5}
{"id": "R-729", "sev": "P3", "category": "Backup & restore", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No route clears the off-site target: controller/internal/web/server.go:744-783 lists /backup/offbox/{config,toggle,enable-all,offer-dismiss,run,reset,status,restore,place,reconstitute,verify-copy/delete,confirm-escrow,inject-password}; /reset (offbox_handlers.go:311) only resets an orphaned repo. New press needs handler + settings clear + template + HU/EN copy + escrow/hub-managed-target interplay — more than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-733", "sev": "P3", "category": "Process & tooling", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Harness/golden-evidence change plus a decision whether proofs run with swap off; no commit references R-733. Not a source-verifiable single fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-738", "sev": "P3", "category": "App updates", "group": "NOT-WORTH-IT", "evidence": "wger part FIXED: app-catalog 7a4ff48 'wger: run the database migrations at start (R-738)'; templates/wger/docker-compose.yml:46 ' - DJANGO_PERFORM_MIGRATIONS=True'. Only the residue stays: the guarded Update's health check reads only the front page.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The guarded Update's health check sees only an app's front page, so an app that serves its front page while its data is broken passes as done.", "cost": "A per-app data read-back inside the product's update check — a new mechanism across every template (the harness fixture already does this off-box).", "if_never": "Catalog onboarding's fixture read-back keeps catching such breaks before a version reaches the ladder; a box could still report done on a broken update the catalog did not test.", "pick": "close-as-accepted (wger defect fixed; the generic gap is a design note)"}, "minutes_spent": 3}
{"id": "R-747", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Lockout shortened: app-catalog a4597cd; templates/mealie/docker-compose.yml:27 ' - SECURITY_USER_LOCKOUT_TIME=1'. Residue still open: hourly lock renewal by a stranger (needs decision 57 option d) and page copy; needs decision + live proof.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-755", "sev": "P3", "category": "Apps & catalog", "group": "DUPLICATE", "evidence": "Still true: templates/wger/docker-compose.yml has no WGER_USE_GUNICORN (grep empty). R-762 (open, read) states 'Owner decides together with R-755 (same server question)' and its fix names 'the gunicorn switch of R-755'.", "dup_of": "R-762", "unique_facts": "wger runs `manage.py runserver` (Django dev server) measured via ps on 9202 2026-10-01; upstream entrypoint.sh:81-87 starts gunicorn only with WGER_USE_GUNICORN=True; the fix needs its own memory watch on bench + box.", "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-756", "sev": "P3", "category": "Storage & devices", "group": "UNCHECKED", "evidence": "Depends on whether 9202's scratch drive is a registered drive — live box state; the row itself says not measured which. Not verifiable from source.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-757", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "No commit references R-757. controller/internal/stacks/deploy.go:1339-1349: 'case \"secret\":' ... 'value, err := generateValue(field.Generate)' ... 'appCfg.Env[field.EnvVar] = value' for any missing field of a deployed app, with no exception for fields consumed only by after_install. Fix needs a design (a marker for given-at-install fields or an ask path).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-758", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Still true: templates/bookstack/.felhom.yml:18 ' mem_limit: \"512M\"' vs compose limits docker-compose.yml:42 '512M' + :79 '256M'; onboarding/EXISTING-APPS-GAPS.md:26 still lists all 8. No gate (only scripts/onboarding_gaps.py:171 reports it). Not small: raising 8 figures changes the capacity check (decision 22) — which apps fit a box — plus a gate with decoy and a publish.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-762", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "templates/wger/docker-compose.yml sets no DJANGO_DEBUG and no static/media server (grep empty); templates/wger/.felhom.yml:18 'lifecycle: hidden' (catalog 55b8c8a). Needs a server design decision (nginx sidecar vs gunicorn+static) and bench+box proof.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-763", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "templates/wger/docker-compose.yml sets neither ALLOW_REGISTRATION nor ALLOW_GUEST_USERS (grep empty); wger hidden (.felhom.yml:18 'lifecycle: hidden'). The env change is tiny but its proof needs a working wger on 9202 (blocked by R-762); best done in the same session as R-762.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-774", "sev": "P3", "category": "Apps & catalog", "group": "STILL-TRUE-NOT-SMALL", "evidence": "templates/karakeep has no Sentry/phone-app sentence (grep -i sentry empty); mail-ON proof needs a hub-enabled live box (demo-hp) — live work.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-775", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Narrowed (Grimmory published behind the family gate). Residue: per-name 15-min lock is hard-coded upstream (no setting) and the reinstall-over-kept-books finding is uninvestigated — needs live investigation.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 2}
{"id": "R-776", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Only bookstack has it: templates/bookstack/docker-compose.yml:32 ' - APP_PROXIES=172.16.0.0/12'; grep for TRUSTED_PROXIES/CORE_TRUST_PROXY/IPEXTRACTION/N8N_PROXY_HOPS in kimai, zipline, vikunja, nextcloud, n8n compose returns nothing. Five apps, each needing a live 3.6 re-measure on 9202.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-778", "sev": "P3", "category": "Security & access", "group": "NOT-WORTH-IT", "evidence": "Reasoned-only row; controller client-address code now in controller/internal/web/clientaddr.go (v0.286+). The window exists only during a self-update crash roll-back below 0.286.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "If a box rolls back to a controller older than 0.286, the dashboard's login counter trusts the leftmost forwarded address and can be dodged until the box moves forward.", "cost": "A 0.285.x patch release or a self-update rule refusing to roll back across 0.286 — release work for a rare window.", "if_never": "The window lasts only from a crash roll-back to the next floor delivery; the floor never moves back. A stranger could make more password guesses during that window.", "pick": "close-as-accepted"}, "minutes_spent": 2}
{"id": "R-782", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Source agrees: templates/glance/docker-compose.yml:27 seeds glance.yml with no auth: block (grep 'auth' in templates/glance empty); templates/homepage has no HOMEPAGE_ALLOWED_HOSTS (grep empty). Needs live measurement on 9202 and a decision whether a public glance dashboard is intended.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-783", "sev": "P3", "category": "Security & access", "group": "NOT-WORTH-IT", "evidence": "Measured upstream behaviour (better-auth 3 per 10 s keyed on one address); SparkyFitness exposes no env for ipAddressHeaders. No source change possible in the catalog.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Three wrong SparkyFitness sign-ins by anyone block every visitor's sign-in for about 10 seconds.", "cost": "Needs an upstream setting (better-auth ipAddressHeaders) plus a right-walking reader — not in our control.", "if_never": "A stranger retrying every 10 s can keep the household out of sign-in; one who stops frees it within 10 s. Not forgeable.", "pick": "close-as-accepted (re-open if upstream exposes the setting)"}, "minutes_spent": 2}
{"id": "R-785", "sev": "P3", "category": "App updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "templates/sparkyfitness/docker-compose.yml:45 ' image: codewithcj/sparkyfitness_server:v0.17.3' and :89 'codewithcj/sparkyfitness:v0.17.3'. Major-version ladder walk (bench + box), gated on R-784.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-831", "sev": "P3", "category": "Security & access", "group": "NOT-WORTH-IT", "evidence": "Operator decision 73: not rotated by choice. Nothing in source to change.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "The Hetzner storage API token was printed into one session transcript.", "cost": "Three manual console/kubectl steps by the operator (about 10 minutes).", "if_never": "Anyone who obtains that transcript could create, reset or delete Storage Box sub-accounts.", "pick": "keep (rotation is the operator's call; do not close a leaked-secret row silently)"}, "minutes_spent": 1}
{"id": "R-836", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Live host boot-loader work needing operator-approved reboots and measurement (GRUB env block on ESP, sp5100_tco arming).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-839", "sev": "P3", "category": "Box system & updates", "group": "STILL-TRUE-NOT-SMALL", "evidence": "Gate behaves as described: controller/cmd/controller/main.go:2397 'return false, \"drive \" + hdd + \" is not a live mountpoint\"' with hdd = cfg.Env[\"HDD_PATH\"] (main.go:2379). Which writer put a per-app path in HDD_PATH is undiagnosed — a diagnosis task, not a one-hour fix.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 4}
{"id": "R-853", "sev": "P3", "category": "Box system & updates", "group": "NOT-WORTH-IT", "evidence": "Still true: felhom-agent e06ed97, cmd/felhom-agent/main.go:3765 'if !f.at.IsZero() && time.Since(f.at) < 10*time.Minute {' caches a failed guest read; main.go:853 factsReporter needs firstGuest(px). The delay equals the 15-min host report interval, so dropping the failure cache alone does not shorten it; the real fix is a guest-less host facts mode in the wrapper.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "After a boot the box's versions and crash facts reach the hub up to about 15 minutes late.", "cost": "A new guest-less facts mode in the root wrapper plus agent change and release.", "if_never": "Facts arrive one report later after each boot; nothing is lost (the crash guard keeps 7 days).", "pick": "close-as-accepted"}, "minutes_spent": 4}
{"id": "R-862", "sev": "P3", "category": "Box system & updates", "group": "UNCHECKED", "evidence": "Waiting on the operator's by-hand bootstrap on Tester 2 through his tunnel; whether done is live-box state. No commit records it (felhom.eu log since 2026-10-04).", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-870", "sev": "P3", "category": "Security & access", "group": "NOT-WORTH-IT", "evidence": "Operator ruling 2026-10-05 option B: not rotated now. Nothing in source to change.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": {"what": "Tester 1's two Cloudflare tokens (disposable test customer) were printed into one session transcript.", "cost": "About 15 minutes of Cloudflare dashboard + hub edit by the operator.", "if_never": "Someone with the transcript could change DNS or the tunnel of the disposable enkicsifelhom.hu test zone.", "pick": "keep (operator's call; close when Tester 1 is retired)"}, "minutes_spent": 1}
{"id": "R-879", "sev": "P3", "category": "Security & access", "group": "STILL-TRUE-NOT-SMALL", "evidence": "hub/internal/store/store.go:166 'retrieval_password TEXT NOT NULL,' and store.go:1586/1592 write retrieval_password and api_key as given; no seal on them (grep seal near these fields empty). Sealing/hashing three tables with migration is a security change, more than an hour.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 3}
{"id": "R-882", "sev": "P3", "category": "Hub & operator", "group": "UNCHECKED", "evidence": "Longhorn instance-manager state on DooPlex (Tier 2, forbidden to touch); live-only, owner operator.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-883", "sev": "P3", "category": "Hub & operator", "group": "UNCHECKED", "evidence": "homelab-manifests repo is not in this workspace (ls /mnt/5_hdd/felhom.eu/git shows only app-catalog-felhom.eu, drills, felhom-agent, felhom-controller, felhom.eu); live DooPlex check (kubectl) is out of scope.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
{"id": "R-886", "sev": "P3", "category": "Monitoring & notifications", "group": "UNCHECKED", "evidence": "DooPlex Alertmanager volume ownership; homelab-manifests not in this workspace and live check not permitted.", "dup_of": null, "unique_facts": null, "small_fix": null, "not_worth": null, "minutes_spent": 1}
@@ -0,0 +1,325 @@
# Burn-down 2026-10-05 — Part A: every P4 and P3 row checked against live source
Method: 8 read-only checker agents, oldest id first (P4 then P3), each row against `main` source (no machine reached); every FIXED / DUPLICATE verdict re-checked by the session before closing (a sample of 22 cited lines re-grepped; one (R-274) held back as half-checked; four (R-700, R-704, R-706, R-723) moved to the operator list — their code fix is in, only the live observation the row waits for is missing). Raw per-row JSON: `partA-results.jsonl`.
Groups: DUPLICATE 2, FIXED-BY-LATER-WORK 29, NOT-WORTH-IT 43, STILL-TRUE-NOT-SMALL 129, STILL-TRUE-SMALL 91, UNCHECKED 23
| Row | Sev | Group | Evidence (abridged) | Min |
|---|---|---|---|---|
| R-10 | P4 | STILL-TRUE-SMALL | FIX: After the os.Rename in DumpOne, open filepath.Dir(finalPath) and call a best-effort dir.Sync() (log at DEBUG on error), mirroring atomicPromoteTar in backup.go:948. — felhom-controller@7690c27 controller/internal/appbackup/dbdump.go:364 `if err := tmpFile.Sync(); err != nil {` then :390 `if err := os.Rename(tmpPath, finalPath); err != nil {` with no directory Sync after; the twin at controlle | 4 |
| R-25 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller@7690c27 controller/internal/web/storage_handlers.go:153 `uuid := resolveEnrollUUID(ctx, agent, device)` still resolves by device PATH after format, then AssignDisk(uuid) at the next step; FormatResult (controller/internal/agentapi/client.go:384-395) carries DurableID only for the confirmation path, not the new fs UUID. Binding resolve+assign to the format's durable-id needs the a | 6 |
| R-76 | P4 | UNCHECKED | Behaviour is FileBrowser-image runtime behaviour (mode/setgid of UI-created folders), only observable on a live box. The image has changed since the finding: controller/internal/infra/infra.go:27 `FileBrowserImage = "gtstef/filebrowser:1.5.6-stable"` (finding was on 1.3.3). The comment at infra.go:207-208 still asserts `umask 002 so folders the customer creates here come out group-writable (2775 w | 5 |
| R-89 | P4 | STILL-TRUE-NOT-SMALL | No retention policy object in hub: `grep -rln -i 'retentionpolicy/retention_policy' felhom.eu/hub` returns nothing (felhom.eu@53d8131b). Commercial per-customer policy = money/product decision + new reconciler. | 2 |
| R-91 | P4 | UNCHECKED | Whether /srv/pbs-felhom still exists on ep0 is live-only (ep0 is protected; not touched). Source-side: CONTEXT.md:3656 still reads "`/srv/pbs-felhom` is 13 G of dead weight on `/` awaiting R-91's go-ahead". Extra fact found: documentation/runbooks/offsite-endpoint.md:24 still says the datastore `felhom-offsite` is at `/srv/pbs-felhom` and :119 `proxmox-backup-manager datastore create felhom-offsit | 5 |
| R-92 | P4 | STILL-TRUE-SMALL | FIX: Add an exact-bytes value to the PBS DR view (e.g. UsedBytesExact rendered as a title= tooltip or a MB-precision string below 10 GB) without changing fmtBytesGB for other callers. — felhom.eu@53d8131b hub/internal/web/pbsdr_box.go:57 and :64 `view.UsedStr = fmtBytesGB(snap.UsedBytes)`; hub/internal/web/offsite_box.go:54 `return fmt.Sprintf("%.1f GB", float64(b)/float64(int64(1)<<30))` — still | 4 |
| R-93 | P4 | NOT-WORTH-IT | PICK close-as-accepted (operator word needed: close, or reopen as 'build a drift fixture'); also drop the dead drill-r50 fence in target-selection.md:111 at close: A row about choosing between two fixtures, neither of which exists any more. | 4 |
| R-99 | P4 | STILL-TRUE-NOT-SMALL | No phantom-snapshot cleanup in felhom.eu/hub or felhom-agent (grep -i phantom finds only agent runner/test detection code; no removal path). Deletion on a customer datastore is a separate operator ruling per the row — customer data. | 3 |
| R-104 | P4 | STILL-TRUE-SMALL | FIX: Add an OffsiteFailLocked class matched by offboxLockRe in ClassifyOffsiteFailure (before transport) and a cause line in OffsiteFailureMessage telling the operator the repository is locked by an interrupted run and how it clears. — felhom-controller@7690c27 controller/internal/backup/offbox.go:193-222 ClassifyOffsiteFailure has cases NoUnits/NoRepo/Transport and `default: return OffsiteFailUnk | 5 |
| R-124 | P4 | NOT-WORTH-IT | PICK close-as-accepted: The disaster-recovery recipe writes the PBS root namespace as the word 'root', but PBS itself uses an empty name, so a pasted '--ns root' fails. | 4 |
| R-129 | P4 | STILL-TRUE-SMALL | FIX: After one read-only `ssh -o BatchMode=yes demo-hp true` (and reading root's authorized_keys comment to name the key), rewrite nodes.md 'Access' section to the measured truth and drop the R-129 caveat in target-selection.md:111-112 (also update the memory index line). — Docs still say no key: felhom.eu@53d8131b documentation/operations/nodes.md:110 `### Access — there is no baked SSH key` and | 4 |
| R-134 | P4 | STILL-TRUE-SMALL | FIX: Extract a pure zoneCandidates(domain) []string that yields the name and every parent down to two labels, and loop resolveZone over it (same order: most specific first). — felhom.eu@53d8131b hub/internal/cloudflare/unblock.go:117 `for _, name := range []string{domain, parentDomain(domain)} {` and :136-141 parentDomain strips exactly one label (`strings.SplitN(domain, ".", 2)`); controller stri | 4 |
| R-161 | P4 | NOT-WORTH-IT | PICK close-as-accepted (residual is a deliberate ruling; owner operator): The runtime check that app data lands on a volume is run by hand, not on every push. | 3 |
| R-162 | P4 | NOT-WORTH-IT | PICK close-as-accepted: If Docker ever ran on a storage driver where `docker diff` does not work, the persistence gate would refuse to report and blame the prober instead of the driver. | 3 |
| R-164 | P4 | STILL-TRUE-NOT-SMALL | Predicate still absent: felhom-controller controller/internal/appbackup/dbdump.go:544 still only WARNs `its accounts table has NO rows`; restore still replays dump + tar (internal/backup/restore_unit.go:114-118 hasReplayableDump). Blocked on a design (live-vs-dump per-table counts). | 3 |
| R-169 | P4 | NOT-WORTH-IT | PICK close-as-accepted (row itself says decide only if the window ever costs something): CI only reports after a push lands, because every repo pushes straight to main with no pull request. | 2 |
| R-177 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller@7690c27 controller/cmd/controller/main.go:1546 `sched.Daily("fill-watch", "03:30", func(ctx context.Context) error { return fillWatcher.Check() })`; internal/scheduler/scheduler.go:269 has GetJobs but grep finds no RunNow/Trigger method and no run-job route in internal/web. Needs a new operator-gated trigger endpoint (auth surface) — a new mechanism, solve together with R-279. | 4 |
| R-184 | P4 | FIXED-BY-LATER-WORK | Fixed by felhom.eu b55fc17d "hub v0.102.0 — refuse to vouch a version that cannot be installed (R-273)" — exactly shape (b), validate at vouch time in the hub. felhom.eu/hub/internal/web/configs.go:1358 `res := s.gitea.PackageDownloadable(ctx, t.pkg, t.version, t.file)` and :1365 `s.logger.Printf("[WARN] artifact vouch REFUSED: %s package %s is NOT downloadable (R-287)", ...)`; tag leg at :1343 Ta | 4 |
| R-194 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Proxmox caches permissions, so a removed storage grant can still read as present for seconds to minutes; our self-repair notices only after the cache expires. | 2 |
| R-206 | P4 | STILL-TRUE-NOT-SMALL | homelab-manifests@87dfc29 (/home/kisfenyo/git/homelab-manifests): no daemon.json template in homelab-ansible (grep finds only a comment at roles/node_housekeeping/templates/node-housekeeping.sh.j2:17 and homelab-ansible/CLAUDE.md:54). Part (b) was superseded by fc9fbb8 ("correct the expired Docker rationale"): the script now says at :13-20 do NOT add docker calls, the GC policy in daemon.json is t | 4 |
| R-207 | P4 | FIXED-BY-LATER-WORK | Fixed by homelab-manifests fc9fbb8 "node_housekeeping: guard DRY_RUN, correct the expired Docker rationale, pin container log rotation". /home/kisfenyo/git/homelab-manifests/homelab-ansible/roles/node_housekeeping/templates/node-housekeeping.sh.j2:137 `if [[ "${DRY_RUN}" == "1" ]]; then` inside write_metrics, :138 logs "file left untouched". | 3 |
| R-208 | P4 | STILL-TRUE-SMALL | FIX: Move the ARG VERSION/GIT_COMMIT (controller) and ARG VERSION/BUILD_TIME (hub) declarations down to just above the final `go build` RUN. — felhom-controller@7690c27 controller/Dockerfile:12 `ARG VERSION=dev` and :13 `ARG GIT_COMMIT=unknown` sit above :19 `RUN go mod download // true`; felhom.eu@53d8131b hub/Dockerfile:3 `ARG VERSION=dev`, :4 `ARG BUILD_TIME=unknown` above :9 `RUN go mod downlo | 3 |
| R-209a | P4 | UNCHECKED | Live-only: whether DooPlex has rebooted and /var/log/felhom-store-postboot-check.log says PASS. Not read (DooPlex is Tier 2, operator ruled no reboot; this pass touches no machine). No source claim to check. | 2 |
| R-210 | P4 | NOT-WORTH-IT | PICK close-as-accepted: 193 old controller/hub images exist only on DooPlex and cannot be re-pulled; the question is whether to delete them. | 2 |
| R-213 | P4 | STILL-TRUE-NOT-SMALL | Row is a not-started design (live-vs-backup comparison, then put-back flow), operator-owned; nothing in source to verify against. | 1 |
| R-230 | P4 | STILL-TRUE-NOT-SMALL | Owed rulings, not code: (a) bulk-correction ruling on MEMORY.md staleness (MEMORY.md index still carries version literals, e.g. 'ctrl 0.224.0', 'hub 0.109.0'); (c) spec-as-failing-test pilot not started. (b) closed. Operator decision required. | 2 |
| R-246 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu@53d8131b hub/internal/store/store.go:3248 `func (s *Store) MarkEscrowStale(hostID string) error {` still has no production caller (grep: only definition + comments at offsite.go:208,216); stale_at still read (store.go:3182 clears it). Ruling owed by operator: evidential setter or retire the column (folds R-248). | 3 |
| R-256 | P4 | STILL-TRUE-SMALL | FIX: Rewrite flash.offbox.mgr_unavailable / mgr_unreachable in both languages to say the backup service is not running yet and give a route (try again in a few minutes; if it persists, contact support). — felhom-controller@7690c27 controller/internal/i18n/locales/hu.json:1406 `"flash.offbox.mgr_unavailable": "A mentéskezelő nem elérhető.",` used at controller/internal/web/offbox_handlers.go:54 and | 4 |
| R-261 | P4 | STILL-TRUE-SMALL | FIX: Reword the doc comment (selfbind.go:106-110) to say it is a test accessor and name the two tests that pin the auto-mint invariant (selfbind_automint_test.go, customer_delete_test.go) — or, if the operator prefers, add one post-mint production check that logs [WARN] when count != 1. — felhom.eu@53d8131b hub/internal/store/selfbind.go:111 `func (s *Store) CountSelfBindTokens(customerID string) | 3 |
| R-263 | P4 | STILL-TRUE-SMALL | FIX: Change the comment to 'the only writer that GRANTS the role' and add a source-scanning test that finds every `.BackupTarget =` assignment in non-test settings code and fails if any other than SetBackupTarget can assign a non-false value. — felhom-controller@7690c27 controller/internal/settings/settings.go:1655 `// from every other. This is the ONLY writer of StoragePath.BackupTarget — registr | 3 |
| R-264 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu@53d8131b scripts/wire_contract_gate.py still allowlists the six with _R264: :242 selfupdate_pending, :246 selfupdate_pending_version, :255 restore_tests.mount_parity, :258 restore_tests.mount_inventory, :281 backup.last_db_dump, :282 backup.last_integrity_check. Each reader is a design per the row. | 3 |
| R-266 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller@7690c27 controller/internal/report/builder.go:94 `{Mount: "/", Label: "SSD", TotalGB: sysInfo.DiskTotalGB, UsedGB: sysInfo.DiskUsedGB, Percent: sysInfo.DiskPercent},` — no disk_known on the storage entry; hub has no disk_known (grep empty). Two-repo wire change gated by wire_contract_gate.py. | 3 |
| R-279 | P4 | STILL-TRUE-NOT-SMALL | No operator/hub path to start an off-site run: grep for offbox run triggers in felhom.eu/hub/internal finds nothing; the only run entry is the customer dashboard handler (felhom-controller controller/internal/web/offbox_handlers.go:270 `if !s.backupMgr.OffboxRunnable() {`). Needs a new operator-authenticated trigger — sibling of R-177, not a duplicate (different job). | 3 |
| R-284 | P4 | NOT-WORTH-IT | PICK close-as-accepted (close as not-a-defect): A reported 'almost full' warning on an empty disk; the code shows the warning only below 20% free and hides it by default, so the report was a reading of unrendered HTML. | 5 |
| R-285 | P4 | STILL-TRUE-NOT-SMALL | No maintenance/expected-downtime concept in hub: `grep -rln -i 'maintenance/expected_downtime/quiet_until/snooze' felhom.eu/hub/internal` returns nothing. New mechanism (M). | 3 |
| R-286 | P4 | STILL-TRUE-SMALL | FIX: Add one paragraph: a positive control must come from a different channel than the measurement (different query path, snapshot, API or clock); give the 2026-08-09 stale-snapshot case as the example. Put it in ONE home (pointer elsewhere). — Lesson (a) not written anywhere: grep -i 'different channel/same channel/independent channel' over documentation/runbooks/workspace-CLAUDE.md, felhom.eu/sk | 6 |
| R-287 | P4 | FIXED-BY-LATER-WORK | Deleter established 2026-08-10 (R-267 newest-10 prune, recorded in the row itself); CI fixed by felhom-agent 53d047a "Two guards, one number: bound the published check to the retention it must live with" (R-291). felhom-agent@e06ed97 scripts/check-published-versions.py:101 `RETENTION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "retention-policy.json")`, :213 `keep = retention_k | 5 |
| R-288 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu@53d8131b documentation/architecture/00-capability-map.md is now 210 125 bytes / 30 937 words / 253 lines (`wc`), larger than the 134 642 bytes measured in the row; :38 still reads `*Verified 2026-07-16 against evidence corpus @ felhom.eu tip `4b18cc5``. Restructure is an M doc surgery, operator-owned. | 3 |
| R-289 | P4 | FIXED-BY-LATER-WORK | R-182 was closed by felhom.eu ef6ac6fe (2026-08-22, register compression): documentation/backlog/CLOSED-ITEMS.md:474 `/ **R-182** / ... / **CLOSED — SHIPPED** (controller v0.194.0 + hub v0.90.0/.1, 2026-08-03) /`. The residue (digest never seen delivering) was since observed: documentation/audits/DRILL-chaos-night-2026-09-17.md:181 `backup_run_failures` „1 of 12 apps failed to back up in this nigh | 5 |
| R-290 | P4 | STILL-TRUE-NOT-SMALL | Gate exists (felhom.eu scripts/check_stands.py) but the map itself still carries the claims: documentation/architecture/00-capability-map.md has 95 'PROVEN-LIVE' occurrences (grep -c); demoting 12 rows or writing walk documents is M and blocked on R-288 per the row. | 3 |
| R-291 | P4 | STILL-TRUE-SMALL | FIX: Rewrite the _comment/recorded_by to cite the operator's newest-10 rule (R-267/R-287) instead of 'observed, not a ruling', and drop or correct the non-existent registry-retention.md reader. Keep the min_agent-floor note as the recorded better bound; then close R-291. — felhom-agent/scripts/retention-policy.json still says the 10 is 'NOT a ruling anyone has been able to locate' and recorded_by: | 8 |
| R-292 | P4 | STILL-TRUE-SMALL | FIX: Make resolveArtifactSHA return a reason (not-found / unreachable / bad manual sha) and redirect to three distinct flashes (reuse artifact_unverifiable for unreachable, add artifact_version_missing, keep artifact_sha_invalid for a bad typed sha incl. the wrapper sha at :1395). — hub/internal/web/templates/configuration.html:55 still reads 'the Gitea sha lookup failed (version missing / Gitea u | 6 |
| R-310 | P4 | STILL-TRUE-SMALL | FIX: Drop the second 'The vouched golden is' sentence when GOLDEN_CHECK_WHY already names it (or drop the version from :3060); add one runbook line: --uninstall needs an interactive terminal; --force does not bypass the typed vmid confirm. — felhom.eu/scripts/felhom-host-install.sh:3060 sets GOLDEN_CHECK_WHY="it is controller $ver, but the vouched golden is $ART_GOLDEN_VER" and :3080-3081 die "... | 6 |
| R-315 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/scripts/wire_contract_gate.py:30-48 still documents the test as a repo-wide literal-tag search ('IT PROVES REACHABILITY OF A NAME'); ROOTS at :88 includes the R-311 escrow/retained root. No receiver-type field-by-field comparison exists. Fix requires resolving receiver mirror types — a new mechanism (M). | 5 |
| R-325 | P4 | STILL-TRUE-SMALL | FIX: Import RETRIEVAL_STEMS from ../felhom.eu/scripts/customer_copy_vocab.py (same sibling-path pattern as controller_gates.py:48) and delete the STEMS literal; absent sibling = INCONCLUSIVE exit 2. Follow-up (felhom.eu, separate commit): remove hub_copy_gate.py's drift check, which would then fail to find STEMS. — felhom-controller/controller/scripts/retrieval_promise_gate.py:54 still has its own | 6 |
| R-327 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/documentation/architecture/where-felhom-stands.yaml:126-130 still: id claim.code-naming, title "The same word is used for two different secrets across three surfaces; the email points at a page a rebuilt machine does not show", status: partial. Needs the operator's capability-map ruling first (dataset may not be raised on its own). | 4 |
| R-331 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/internal/agentapi/diskverdict.go:34 uncorrectableFailCount = 64; :33 comment still defers 'growth-rate detection once the box keeps history'. No growth-rate rule found. New mechanism (M). | 4 |
| R-336 | P4 | STILL-TRUE-NOT-SMALL | No source change reduces the ep0 poll rate (pvestatd interval is Proxmox-side, not in our repos). Design question (does the hub need a 15-min fill reading) remains; acceptance needs an ep0 access-log measurement. Scaling item, not small. | 3 |
| R-337 | P4 | UNCHECKED | Live-only behaviour (WATCHING). From source: felhom-agent/internal/localapi/server.go:518 serves GET /backup/status and :1258 answers from s.pickLatestBackup (the in-memory store), which suggests collection cadence, but the refresh path after an out-of-schedule run was not established within the time box. | 6 |
| R-345 | P4 | STILL-TRUE-SMALL | FIX: Delete lines 21-22 (or move them behind an explicitly named opt-in target with a comment). Whether a stale :latest already sits on the registry is a separate live check for a session allowed to query it. — felhom.eu/hub/Makefile:21 'docker tag $(IMAGE):$(VERSION) $(IMAGE):latest' and :22 'docker push $(IMAGE):latest' still present; only commit touching the Makefile is 77b5a4ce (initial). | 3 |
| R-346 | P4 | NOT-WORTH-IT | PICK close-as-accepted (audit done, zero instances): A warning that a future reader might anchor an uptime slope on systemd's ActiveEnterTimestamp instead of the process start time. | 4 |
| R-348 | P4 | STILL-TRUE-SMALL | FIX: Reword the comment: the backups list IS lost on restart and refills only when a backup runs; the hub's freshness VERDICT is unaffected because it looks back 7 days (hub monitor/deadline.go backupEvidenceLookback, pinned by deadline_anchor_test.go TestCheckBackupDeadlines_RestartBlindWindow_NoEvent). — felhom-agent/internal/backup/store.go:28 still reads '// Backups are unaffected — their fres | 6 |
| R-352 | P4 | STILL-TRUE-NOT-SMALL | Placement half is an open operator ruling (SPEC-app-data-placement-2026-08-21.md, 'Viktor rules'); deploy route still has no server-side default: GetDefaultStoragePath has no caller in internal/stacks (grep returns only internal/api/router.go:1137 systemInfo). Point (2) is carried by R-368. | 4 |
| R-364 | P4 | STILL-TRUE-SMALL | FIX: A helper that, for a pattern containing a byte >= 0x80, also runs an ASCII anchor (must hit) and a negative control (must miss) and refuses to print a zero unless both behave; documented in the felhom-evidence or ui-hungarian rule as the way to search Hungarian text. — No helper exists: ls felhom.eu/scripts shows nothing grep/accent-related, and no script mentions '0x80' or 'negative control' | 4 |
| R-365 | P4 | STILL-TRUE-SMALL | FIX: Set AbandonOverdue when DueAt is past and render a new key ('a törlés esedékes, a következő napi karbantartáskor lefut' / English twin) instead of the future-tense sentence. — felhom-controller/controller/internal/i18n/locales/hu.json:368 'A kérésed szerint a korábbi távoli mentéseidet <strong>{{.AbandonDate}}</strong> napján véglegesen töröljük (még ... nap)'; handlers.go:1164-1167 sets Aban | 5 |
| R-367 | P4 | NOT-WORTH-IT | PICK close-as-accepted (or delete it the next time the box is reprovisioned): One old 312 KB paperless database dump on the demo-hp test box sits under the app's old folder name; nothing reads or deletes it. | 4 |
| R-368 | P4 | STILL-TRUE-SMALL | FIX: Reword the field comment: the deploy FORM pre-selects this path (templates/deploy.html:614); the deploy API applies no default when HDD_PATH is omitted. (The alternative — a server-side default — is a behaviour change and not small.) — felhom-controller/controller/internal/settings/settings.go:572 'IsDefault bool `json:"is_default,omitempty"` // new apps use this by default' (line moved from | 5 |
| R-371 | P4 | NOT-WORTH-IT | PICK close-as-accepted with one line in 07-backup-architecture saying success is silent by design because failure and staleness are alarmed: The weekly off-site backup sends no 'done' event, while the two local tiers do. Failures and an 8-day staleness deadline are already alarmed. | 6 |
| R-372 | P4 | NOT-WORTH-IT | PICK close-as-accepted: An optional idea from July: show 'this second-drive copy was never made because its source is missing' separately from 'last copy failed' in the operator screen. | 5 |
| R-373 | P4 | FIXED-BY-LATER-WORK | Premise (20G/50G two-volume mismatch, 'nothing sets SysDataGrowGB') was retired by agent v0.120.0 one-data-volume work, commit cd6e267 'v0.120.0 — one data volume (R-165...)'. felhom-agent/internal/reconcile/bringup.go:191 '// SysDataGrowGB is a COMPATIBILITY INPUT since agent v0.120.0 (R-165). There is no longer a second' and :437 'growGB := spec.DataVolGrowGB + spec.SysDataGrowGB'; installer pas | 6 |
| R-374 | P4 | NOT-WORTH-IT | PICK close-as-accepted, with one line in the audit saying the three are not recoverable: A July audit says three borderline cases were left unfiled but never named them. | 5 |
| R-375 | P4 | UNCHECKED | Requires a read-only check on ep0 (token's datastore audit permission); not verifiable from source and ssh is out of scope for this checker. | 2 |
| R-376 | P4 | STILL-TRUE-SMALL | FIX: Carry the same marker legend paragraph into the three documents written after the 2026-08-22 pass; then close the row, since 'mark as sessions touch them' is a standing practice already in the template, not a defect. — Legend present ('not yet classified') in 00..06 and 10 of documentation/architecture/, but MISSING in the newer 08-alarm-ladder.md, 09-update-architecture.md and 11-os-updates. | 5 |
| R-377 | P4 | STILL-TRUE-SMALL | FIX: Turn each ruling's opening bold line '**S-NN — TITLE (date ...).**' into a '### S-NN — TITLE (date)' heading, changing no other byte; no compression, no reordering. — felhom.eu/CONTEXT.md:1537 '## Standing rulings' runs to EOF: 189,685 bytes, 0 '###' sub-headings, 153 bullets, 39 distinct S- ids; each ruling starts as a bold paragraph e.g. '**S-39 — "WE DO NOT KNOW" IS NEVER DRAWN AS "FINE".. | 6 |
| R-390 | P4 | FIXED-BY-LATER-WORK | Commit 2344589a ('... runbook pveam note'); felhom.eu/documentation/runbooks/RUNBOOK-manual-build.md:154 '2. Run **`pveam update` first** — the `virgin` snapshot's template INDEX is stale too, and a stale index fails as a bogus'. | 3 |
| R-391 | P4 | STILL-TRUE-SMALL | FIX: Take the row's second option: state in CLAUDE.md that the catalog REPORT.md carries no observations section by convention (findings go straight to the register), so gate 11 is not needed here. The runner refactor (first option) is the bigger alternative. — app-catalog-felhom.eu/scripts/catalog_gates.py has no SHARED_ / observations entry (grep 'SHARED_/observations' returns nothing); app-cata | 4 |
| R-392 | P4 | STILL-TRUE-NOT-SMALL | ls felhom.eu/documentation/architecture shows no agent-tooling/workflow document (00-11 are all product; plus _design-review, _hub-review, _recovery-inventory). Writing a new architecture document is more than an hour and needs the operator's view of the split. | 3 |
| R-393 | P4 | NOT-WORTH-IT | PICK close-as-accepted (superseded in practice by unprompted-work.md §2/§4): A proposed skill plus helper script to log every decision an unattended run makes. | 4 |
| R-394 | P4 | STILL-TRUE-NOT-SMALL | wc -l felhom.eu/skills/felhom-build-deploy/SKILL.md = 186 (was 179 at filing — grew); scripts/check_skills.py:45 GRANDFATHERED still holds the exemption. Trim needs a session that can verify the build/deploy commands it keeps. | 3 |
| R-402 | P4 | STILL-TRUE-NOT-SMALL | No hub Go/template reads last_integrity_ok/_depth (grep in hub/internal returns nothing); still allowlisted at felhom.eu/scripts/wire_contract_gate.py:163 and :220. Needs the operator's decision on what the screen says. | 3 |
| R-416 | P4 | STILL-TRUE-SMALL | FIX: Apply the existing RULE 3 duplicate check to CLOSED-ITEMS.md too (suffixed ids like R-88a/R-88b stay distinct), and update the closed_register_gate.py:53 hole list to point at it. — Partly covered: scripts/register_shape_gate.py:120 'RULE 3 — duplicate: {rid} already has a row at line ...' (added in 462ab4a5, R-627) now refuses a duplicate id WITHIN OPEN-ITEMS.md only (REG path at :84 = OPEN- | 7 |
| R-418 | P4 | STILL-TRUE-SMALL | FIX: Add the two missing gates to the docstring list and a test that parses the docstring's gate labels and asserts they equal [g[0] for g in GATES]. — It drifted AGAIN: felhom.eu/scripts/repo_gates.py docstring lists 1-14 (+9b) = 15 gates while GATES has 17 — 'script-tests' and 'decoy-coverage' are registered but not listed (python import: len(GATES)=17). No test compares the two. | 5 |
| R-420 | P4 | NOT-WORTH-IT | PICK close-as-accepted (add it with the first gate that needs it): The felhom.eu gate runner cannot mark a gate as advisory-only; the controller runner can. | 3 |
| R-421 | P4 | STILL-TRUE-NOT-SMALL | Deliberate class row ('stays open as the place the next instance is recorded'); its open instances R-422..R-426 are still open in this batch. Not a fixable item by itself. | 2 |
| R-422 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/scripts/reuse_refs_check.py:41 PATH_RE still ends '\.(?:go/py/html/css/yml/yaml/sh)\b' — no .md. Widening it needs a false-positive walk across all four repos' REUSE.md/CLAUDE.md citations (the row says that pass is the work). | 3 |
| R-423 | P4 | STILL-TRUE-SMALL | FIX: Discover website/**/*.html and FAIL on any page not in PAGES (or glob and keep PAGES only as exceptions); flip the decoy test to expect conviction and drop the 'site' EXEMPT entry. — felhom.eu/scripts/site_gates.py:22-27 hardcoded PAGES list; website/ today holds exactly those 9 files, so nothing is missed today, but a new page is not scanned. | 4 |
| R-424 | P4 | NOT-WORTH-IT | PICK close-as-accepted (hole stays declared in the gate's docstring): The roadmap gate cannot tell a real defect filed as an 'idea' from a genuine idea. | 3 |
| R-425 | P4 | STILL-TRUE-SMALL | FIX: Scan by pattern (templates/backups*.html, *offbox*.go) plus internal/i18n/locales/hu.json, or assert FILES against a discovered set so an unclassified file fails; drop its decoy-coverage exemption. — felhom-controller/controller/scripts/offbox_rename_gate.py:16-20 FILES = backups.html, offbox_handlers.go, offbox.go only; templates/backups_remote.html exists and is not scanned, and customer co | 6 |
| R-426 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/scripts/decoy_coverage_gate.py EXEMPT now has 19 entries (loaded via python): hub-copy is gone, felhom-agent 'release-complete' is new; group (d) gates (hostinstall, wire-contract, due-checks, published, image-resolvable, volume-persistence) all still exempt. | 5 |
| R-427 | P4 | FIXED-BY-LATER-WORK | Commit 71b8c8c6 (Backlog triage Part B: '... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS'); felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word (CLOSED, SHIPPED,'. Of the 12 named rows, R-385/387/341/378/405/88a/88b/123 are now only in CLOSED-ITEMS.md; R-190 and R-352 remain open (partly-closed, as the ro | 5 |
| R-437 | P4 | FIXED-BY-LATER-WORK | felhom.eu 71b8c8c6 'Backlog triage Part B: 125 finished rows + 20 id-less rows moved to CLOSED-ITEMS ... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS (decoys, seen red) ... register 444 -> 325'. felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word'. Gate run today: 'closed-register gate OK — no open work fi | 4 |
| R-445 | P4 | NOT-WORTH-IT | PICK close-as-accepted: The hub's per-app memory suggestion can be built from samples of an app that no longer runs anywhere (e.g. a 15-minute test install). | 5 |
| R-451 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/internal/report/types.go ContainerDetailReport still carries only Name/State/CPUPercent/MemoryMB (no image field). Ruled (09 §3 decision 18), build deferred until fleet grows; needs controller payload + hub denormalisation + fleet page across two repos. | 3 |
| R-454 | P4 | STILL-TRUE-SMALL | FIX: Add a gofmt -l gate (fails on any listed file, INCONCLUSIVE if gofmt missing) to controller_gates.py, see it red on today's tree, then one gofmt -w formatting commit for the 12 files. — The five files named are now clean (gofmt -l controller/internal/web/ prints nothing), but `gofmt -l controller` in felhom-controller lists 12 OTHER files today: cmd/controller/main.go, internal/agentapi/diskv | 5 |
| R-457 | P4 | STILL-TRUE-SMALL | FIX: Read the six candidates; for each date literal that feeds an assertion evaluated against time.Now(), derive it from now (as the R-457 fix did). The faked-future-date CI instrument is a separate, larger idea and should be split out or dropped. — No later commit references R-457 beyond the filing release (felhom-controller 38d28b5 v0.234.0 / 998aa31 REPORT). No faked-clock CI run exists (grep f | 4 |
| R-460 | P4 | NOT-WORTH-IT | PICK close-as-accepted: BookStack's uploaded files cannot be checked automatically after an upgrade; only its database can. | 2 |
| R-464 | P4 | FIXED-BY-LATER-WORK | Lesson homed and harness uses the correct probe. app-catalog-felhom.eu b7ef0c4 'upgrade-test.py: record the engine's own view of its datadir'; app-catalog-felhom.eu/scripts/upgrade-test.py:278 '"mariadb-upgrade --check-if-upgrade-is-needed --user=root "'. felhom.eu d6837d98 (SPIKE R-459); felhom.eu/documentation/architecture/09-update-architecture.md:1647 '1. **Ask the engine, not the log.** Maria | 4 |
| R-488 | P4 | UNCHECKED | The claim is a measured suite runtime (5.5 min); confirming it needs running go test ./internal/backup, which I did not run (read-only; backup tests may reach real docker on DooPlex). No commit after filing (felhom-controller 24d7c54) mentions R-488 or a test-speed change in internal/backup (git log --grep on internal/backup since 2026-09-13: empty), so it is likely still true. | 3 |
| R-492 | P4 | STILL-TRUE-SMALL | FIX: Remove Paths.HDDPath, its env binding and each reader's dead global branch, keeping the per-app/discovered fallbacks each reader already uses. — cfg.Paths.HDDPath still defined and read: controller/internal/config/config.go:167 'HDDPath string `yaml:"hdd_path"`', :453 envStr("FELHOM_PATHS_HDD_PATH", &cfg.Paths.HDDPath); readers internal/report/builder.go:69, internal/monitor/healthchec | 4 |
| R-494 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/hub/internal/cloudflare/ holds only unblock.go; no tunnel/DNS creation code in hub (grep cfd_tunnel: none). Building it is a new Cloudflare-API mechanism on the hub; operator ruled it non-blocking. | 3 |
| R-501 | P4 | FIXED-BY-LATER-WORK | felhom.eu a4993272 'CLAUDE.md: the CI-check recipe was wrong in two ways, both measured today'. felhom.eu/CLAUDE.md:176 'rows — a run can sit several pages earlier. **Scan every page** and match on `head_sha`; with a'; recipe at CLAUDE.md:166-168 loops every page. | 3 |
| R-502 | P4 | STILL-TRUE-SMALL | FIX: Register bootstrap-modes.sh in repo_gates.py behind a docker-available check that reports INCONCLUSIVE (never pass) when docker/the felhom-iso-assistant image is absent, plus a decoy (a broken banner must turn it red). — felhom.eu/scripts/iso/test/bootstrap-modes.sh exists; `grep -rn 'bootstrap-modes/bootstrap_modes' scripts/*.py .gitea/workflows` in felhom.eu returns nothing — no gate or CI | 3 |
| R-503 | P4 | NOT-WORTH-IT | PICK close-as-accepted (as DECLINED by ruling; the three measurements stay in the closed row for any future reversal): An idea, offered and not chosen: the installer would pick the disk itself when there is exactly one. | 2 |
| R-504 | P4 | UNCHECKED | The claim (iso.felhom.eu/ returns 404) is live-only; I may not curl hosts. felhom.eu/documentation/runbooks/VOLUNTEER-first-hour.md:14 still says '`iso.felhom.eu/` itself still has no index — R-504'. Fix needs a Cloudflare rule the operator owns. Cosmetic; households use felhom.eu/letoltes (website/letoltes.html exists). | 3 |
| R-507 | P4 | STILL-TRUE-NOT-SMALL | Needs measuring QEMU input-send-event or a VNC client against a live VM on felhom-pve — a live-machine spike, not a source change. No later commit references R-507. | 2 |
| R-525 | P4 | STILL-TRUE-NOT-SMALL | Row itself states it is a new unmeasured mechanism (forwardAuth / Quantum proxy auth) needing a scratch-guest spike. | 1 |
| R-526 | P4 | STILL-TRUE-NOT-SMALL | felhom.eu/hub/internal/tenantsync/client.go:110 '// Deprovision DESTROYS the customer's PBS namespace, all its backup groups, and its token — the'; no token-only op exists. Needs a new op on protected ep0 and an operator yes/no. | 3 |
| R-527 | P4 | NOT-WORTH-IT | PICK close-as-accepted (with the corrected facts: flag read into LockedFields, enforced only by uncalled UpdateStackConfig): A catalog flag that marks some settings 'locked after install' changes nothing visible: the page makes every setting read-only anyway, and the edit path that would honour the flag is never called. | 8 |
| R-532 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Vaultwarden's web page shows a sign-up form even though sign-ups are off; the server then refuses it. | 3 |
| R-541 | P4 | STILL-TRUE-NOT-SMALL | Row: needs a new copy/re-key/release mechanism and a design; no later commit references R-541. Far off per re-rank (0.3% full, one pool box). | 2 |
| R-544 | P4 | STILL-TRUE-SMALL | FIX: Change the log line to state the effect, e.g. 'host deleted: %s (escrow custody demoted to retained)' when escrow existed, and drop the boolean name from the text. — felhom.eu/hub/internal/web/hosts.go:917 's.logger.Printf("[INFO] host deleted: %s (escrow deleted: %v)", hostID, deleteEscrow)'. | 3 |
| R-551 | P4 | NOT-WORTH-IT | PICK close-as-accepted (add 'walk R-546 readiness branches' to the next fresh-install checklist instead): The escrow 'waiting for the agent' screens are proven by tests but never seen on a real box in that state. | 2 |
| R-555 | P4 | STILL-TRUE-SMALL | FIX: Strip Go // and /* */ comments and template {{/* */}} before TOKEN_RE in receiver_tokens; add a decoy 'tag named only in a receiver comment must convict'. Newly surfacing tags each become a finding (allowlist with reason or a row). — felhom.eu/scripts/wire_contract_gate.py:451 'def receiver_tokens(repo_root):' tokenises whole files: line 482 'toks.update(TOKEN_RE.findall(fh.read()))' — no com | 3 |
| R-564 | P4 | STILL-TRUE-SMALL | FIX: Add split-form Hungarian patterns (állíthatók? vissza, (hoz/szerez/nyit)\w* vissza), register the Hungarian occurrences found (the seven already reviewed in English), and add a planted split-verb decoy. — felhom-controller/controller/scripts/retrieval_promise_gate.py:54 'STEMS = ["visszaállíthat", "visszaszerezhet", "visszahozhat", "visszanyit"]' — joined forms only, no split-verb pattern. | 3 |
| R-567 | P4 | STILL-TRUE-SMALL | FIX: Add (eq .Page "storage_init") (eq .Page "storage_attach") to $storageOpen and mark the Meghajtók link active for them. — controller/internal/web/templates/layout.html:83 '{{$storageOpen := or (eq .Page "storage") (eq .Page "storage-network")}}' — storage_init/storage_attach not included; storage_handlers.go:351 'data := s.baseData(tmpl, title)' passes the template name as Page. | 4 |
| R-568 | P4 | STILL-TRUE-SMALL | FIX: Sort rows by diskKey(d) (durable id, falling back to name) before returning. — controller/internal/web/disk_health.go:124-152 diskHealthRows appends rows in resp.Disks order; no sort in the file (grep 'sort.' in disk_health.go: none). | 3 |
| R-569 | P4 | STILL-TRUE-SMALL | FIX: Add KindErrorf-style sentinels in internal/stacks (protected, not found, not deployed/still running, not orphaned), a statusFor helper per handler family replacing the three Contains blocks. — controller/internal/api/router.go:742 'if strings.Contains(err.Error(), "protected") {', also :745, :1018, :1021, :1024 ('not deployed'/'still running'), :1102, :1105, :1108 ('not orphaned'). | 3 |
| R-570 | P4 | STILL-TRUE-NOT-SMALL | Fallback still present: controller/internal/web/handlers.go:1050 'offboxStaleWarningMarker = "nincs mentésre jelölt alkalmazás"'; producer internal/backup/offbox.go:1168. Closing depends on a fleet condition (every box one off-site run on >=0.251.0) — a watch, not a fix. | 3 |
| R-571 | P4 | STILL-TRUE-SMALL | FIX: Add a short section to 07 listing the six failure classes, what each means for the customer, and that restic/ssh signatures are external; add an alert-placement paragraph (inline under storage bars vs top banner) to 02. — grep ClassifyOffsiteFailure/PageOnly/Inline in felhom.eu/documentation/architecture/07-backup-architecture.md and 02-controller-module-map.md: no hit. Classifier lives at fe | 3 |
| R-574 | P4 | STILL-TRUE-NOT-SMALL | controller/internal/web/handler_debug.go still carries 40 lines with accented Hungarian string literals (grep -cP count); last touched by 0c702f8 v0.279.0, not converted. Labelling ~40 literals page-copy vs payload, adding en/hu keys and parity fixtures exceeds an hour. | 3 |
| R-576 | P4 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/scripts/i18n_go_parity.py has no call-site argument-count or '+'-adjacency check (grep verb/argument: only VERB_RE/strip_verbs for text equality, lines 76-78, 196-199). Parsing multi-line Go call arguments reliably from Python with decoys is likely >1 h. | 4 |
| R-577 | P4 | STILL-TRUE-NOT-SMALL | Waiting on an operator decision (what the share feature promises a stranger); felhom.eu/documentation/architecture/10-localisation.md table row 'the two guest share pages, the catch-all / a stranger / nobody / **no globe** / — (R-577, the operator's)'. | 2 |
| R-579 | P4 | STILL-TRUE-NOT-SMALL | Gate deliberately deferred until R-554 deletes the first-boot wizard; R-554 is still OPEN (OPEN-ITEMS.md:131). Versionless links remain in controller/internal/setup/templates/setup_*.html:8 '<link rel="stylesheet" href="/static/style.css">' (8 files). | 5 |
| R-588 | P4 | STILL-TRUE-SMALL | FIX: Name the single home documentation/tests/iso-release-<ver>-<date>/ in the Result-recording section, and add a pointer dir/README for 1.28.0 to its audit record (evidence-backup-promise-2026-09-16/phaseD-iso-gate.txt). Optional existence check left out. — felhom.eu/documentation/runbooks/iso-release-gate.md:319-322 'Result recording' says only 'in the release report' — names no home. documenta | 4 |
| R-591 | P4 | STILL-TRUE-SMALL | FIX: Deep-copy Meta.I18n in deepCopyStack (map plus nested values). — The copy is deepCopyStack in controller/internal/stacks/manager.go (row says Copy()); it deep-copies AppConfig (:1137-1148), DeployFields (:1162), OptionalConfig (:1174), Integrations (:1186) and has no I18n line (grep I18n in manager.go: none). Meta.I18n defined at internal/stacks/metadata.go:94. | 4 |
| R-594 | P4 | STILL-TRUE-SMALL | FIX: Add ALLOWLIST_EN of (app, path, reason); registered occurrences pass, unregistered convict, and any entry matching nothing is itself a failure. — app-catalog-felhom.eu/scripts/check-copy-i18n.py: grep ALLOWLIST/allowlist — no hit; no way to register a true occurrence. | 3 |
| R-599 | P4 | STILL-TRUE-SMALL | FIX: Make both 409 bodies say when the last report arrived and when deletion opens (last report + configured stale threshold), and name the wait in target-selection.md's drill section. — felhom.eu/hub/internal/web/hosts.go:898 'http.Error(w, "Host is ONLINE — deletion is refused (a live agent would receive 401s permanently).", http.StatusConflict)' — no last-report age or opening time. target-sele | 4 |
| R-602 | P4 | FIXED-BY-LATER-WORK | felhom.eu e02bc038 'hub v0.119.0 — ... R-596/R-598 closed' added the finding; felhom.eu/documentation/architecture/10-localisation.md:809-812 'the `felhom_lang` cookie and got the **Hungarian** page for `en`. ... The cookie is the right instrument for the anonymous claim page and the **wrong**' and :509 '`langFor`'s order is fixed: `?lang=` → **the household's setting when a session exists**'. Onl | 4 |
| R-603 | P4 | STILL-TRUE-SMALL | FIX: Add a test helper that compares against html.EscapeString(want) and use it in the render tests that assert English copy; the bundle gate with a 27-entry allowlist is the larger alternative. — No gate or helper: grep for html.EscapeString(want/&#39;/R-603 in felhom-controller/controller scripts+internal: none. controller/internal/i18n/locales/en.json has 27 lines containing an apostrophe today | 4 |
| R-605 | P4 | STILL-TRUE-SMALL | FIX: Give harness-level refusal its own exit code (e.g. 3 = REFUSED) in both scripts and map it to a distinct label in catalog_gates.py. — app-catalog-felhom.eu/scripts/catalog_gates.py:122 'VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}' — harness refusal and per-app undetermined both exit 2 and print the same word. | 3 |
| R-610 | P4 | NOT-WORTH-IT | PICK close-as-accepted: A power cut landing inside the sub-second `starting` phase has never been measured; all three live cuts landed in `verifying`, which runs the same recovery code. | 5 |
| R-617 | P4 | FIXED-BY-LATER-WORK | felhom.eu@462ab4a5 (2026-09-22) documentation/architecture/09-update-architecture.md:1969 "`POST /api/v1/repos/migrate` is the route that works (the project's Gitea tokens carry" - continues at :1970 "`write:repository` but not `write:user`, so `POST /user/repos` answers 403"; recipe at :1979. The one-line note the row asked for exists (in the architecture doc rather than operations/). The optiona | 4 |
| R-618 | P4 | STILL-TRUE-NOT-SMALL | Remaining open work = the controller-side idea (let `verifying` accept docker's own `healthy`). grep for docker health status in felhom-controller@7690c27 controller/internal/stacks/update.go returns nothing; no R-618 reference in Go source. It is an undecided design question (operator), not a defect; the three probe fixes (app-catalog@793c4fb) and the gate scripts/check-probe-matches-compose.py a | 4 |
| R-619 | P4 | STILL-TRUE-SMALL | FIX: In getDeployFields, copy meta.DeployFields and set Required=true for every field with Type=="password" before writing the response (copy, do not mutate the shared metadata; the web deploy page uses GetDeployFields separately and is untouched). — felhom-controller@7690c27 controller/internal/api/router.go:395 `meta, appCfg, err := r.stackMgr.GetDeployFields(name)` then :402 `"metadata": meta | 6 |
| R-621 | P4 | STILL-TRUE-NOT-SMALL | Capture is done: felhom-controller@7690c27 controller/internal/stacks/update.go:1054 `outDir := filepath.Join(dir, "hold-logs", ts)`. The open part (show it on the app page's hold panel / logs fallback) is not built: grep for hold-logs/holdLogs in controller templates and handlers returns only update.go:1050-1082 and undo.go:535,550 (writers). Surfacing needs a page change with HU/EN copy and a de | 4 |
| R-624 | P4 | STILL-TRUE-NOT-SMALL | Row's own latest update: remaining class is vaultwarden (closed sign-up by design) and code-server; the open decision is whether the harness may hold an app's admin secret (operator). Not verifiable further from source; needs a decision, not a fix. | 2 |
| R-644 | P4 | UNCHECKED | About the live state of gokapi on scratch guest 9202 (crash-loop, deployed:true). Only the box shows it; no ssh allowed. | 1 |
| R-652 | P4 | STILL-TRUE-NOT-SMALL | app-catalog@917a779 templates/romm/.felhom.yml:249 still carries `"memory_peak_pct": 80.9` with `"memory_tight": true` and no `memory_basis: anon` (contrast paperless-ngx/.felhom.yml:249 `"memory_basis": "anon"`). Needs a live re-measure of romm plus an undecided cache-thrash rule. | 4 |
| R-654 | P4 | NOT-WORTH-IT | PICK close-as-accepted: opengist 1.15 moved its pages under /-/; an old /login bookmark answers 404. The front page redirects correctly. | 3 |
| R-687 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Three live proofs a scratch box cannot give (a 3-hour leg, a failing off-site leg, a files_may_change step without a whole copy) plus one log text that names the window's deadline instead of a manually started leg's deadline. | 5 |
| R-688 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Deleting a customer does not remove their Cloudflare tunnel and DNS records; the dialog now says so and lists what to remove by hand. | 4 |
| R-691 | P4 | STILL-TRUE-NOT-SMALL | Open work = the Tier 2 (second-drive) path of Use/Load is not live-proven; needs a two-drive Tier-0 box (9202 has one drive). Live-only gap, not a source defect; not checkable from source. | 2 |
| R-693 | P4 | STILL-TRUE-NOT-SMALL | grep for memory_scales_with_limit / scales_with_limit across app-catalog-felhom.eu, felhom-controller/controller and felhom.eu/scripts returns nothing - no basis that tells growth-to-fill from pressure exists. Needs a design (new harness signal). | 3 |
| R-705 | P4 | FIXED-BY-LATER-WORK | The remaining half (manual whole-guest backup) EXISTS and predates the row: felhom-controller bbed5af (v0.47.0, 2026-06-12) 'backups page — whole-guest backup visibility + manual trigger'. Live source @7690c27: controller/internal/web/backup_handlers.go:324 `case r.URL.Path == "/api/guest-backup/trigger" && r.Method == http.MethodPost:`; :340 `if err := s.backupTrigger.TriggerNow(); err != nil {`; | 8 |
| R-707 | P4 | STILL-TRUE-NOT-SMALL | Open work = live proof that the gate OPENS for seerr, outline and rallly (needs a media server / e-mail on a test box). Live-only proof gap; the gating itself is in source (catalog 6faf432 per row). | 2 |
| R-718 | P4 | STILL-TRUE-SMALL | FIX: Add a key app_info.close_signup_restart ("The app restarts once for this." / HU twin) and render it under the close card when the app has an after_setup env switch (the same SignupNative fact); add the same sentence to the gate-open confirmation where after_setup.env exists. — felhom-controller@7690c27 controller/internal/web/templates/app_info.html:112 `<p>{{T "app_info.close_signup_text"}}< | 6 |
| R-719 | P4 | STILL-TRUE-NOT-SMALL | Built: felhom.eu hub/internal/web/selfbind.go:160 "// R-719 (v0.126.0): „Új linket kérek" on an expired or used link." Open part is the operator's review of the changed shape and a live mint+send proof (unit-proven only) - an operator decision, not a CC fix. | 3 |
| R-725 | P4 | STILL-TRUE-SMALL | FIX: Reword bind.invalid.body to point at the button below (e.g. 'Ha lejárt, kérj újat lent.' / 'If it has expired, ask for a new one below.'), keeping the operator alternative; optionally drop the ✔ glyph the console font renders as 'V' and update the golden. — felhom.eu hub/internal/i18n/locales/hu.json:79 `"bind.invalid.body": "A hivatkozás 7 napig érvényes. Ha lejárt, kérj újat az ügyfélszolgá | 6 |
| R-731 | P4 | STILL-TRUE-NOT-SMALL | The shape-switch control lives only in audit tools: felhom.eu/documentation/audits/catalog-currency-2026-09-30/00-currency.py, 04-analyse.py; no standing currency script in app-catalog-felhom.eu/scripts or felhom.eu/scripts (ls/grep for currency/shape returns only golden_currency_gate.py, which is unrelated). Making it standing means promoting a registry-reading tool with tests - more than an hour | 4 |
| R-734 | P4 | STILL-TRUE-NOT-SMALL | grep for '.immich' / hash ignore list in app-catalog-felhom.eu/scripts/*.py and templates/immich/.felhom.yml returns nothing - no exclusion exists. The row says the rule change needs an operator word; calibre-web shows the mark is sometimes right, so the rule needs design. | 3 |
| R-739 | P4 | STILL-TRUE-NOT-SMALL | app-catalog@917a779 templates/wanderer/docker-compose.yml:117 `image: getmeili/meilisearch:v1.36.0`; grep MEILI_UPGRADE_DB in the compose returns nothing. Remaining: the template switch, a fixture (PocketBase create refused) and a measured step on the bench - live work. | 3 |
| R-759 | P4 | STILL-TRUE-NOT-SMALL | Five checklist rows of wger need live measurement on 9202 (2.5, 3.7, 6.3, 8.2, 9.1); not verifiable from source. | 2 |
| R-760 | P4 | STILL-TRUE-SMALL | FIX: Read the vikunja 2.6.0 image config for a HEALTHCHECK/shell; if none and the image has no shell, add a comment saying why there is no compose healthcheck (like adventurelog-frontend's R-655 comment); otherwise add a healthcheck of the family the image supports (REUSE.md §2). No image: line moves, so no catalog_since. — app-catalog@917a779 templates/vikunja/docker-compose.yml: service vikunja | 4 |
| R-761 | P4 | STILL-TRUE-SMALL | FIX: Change the comment to name `{slug}-logo.svg` (preferred) and `{slug}-logo.png` (fallback), matching config.go AppLogoURL/AppLogoPNGURL; comment-only. — app-catalog@917a779 templates/paperless-ngx/.felhom.yml:22 `# Logo: {assets.base_url}/assets/{slug}-logo.webp` vs felhom-controller controller/internal/config/config.go:511 `return fmt.Sprintf("/static/assets/%s-logo.svg", slug)` and | 3 |
| R-764 | P4 | STILL-TRUE-NOT-SMALL | grep smtp/mail in app-catalog templates/wger/.felhom.yml and docker-compose.yml finds only first_steps text (.felhom.yml:74 'Add meg az email címedet a beállításokban'); no smtp_mapping. A mapping needs a live boot proof with mail off (REUSE.md §2) - more than an hour; wger is hidden. | 3 |
| R-766 | P4 | FIXED-BY-LATER-WORK | Hub releases after the assets push (felhom.eu 40f07429, 2026-10-01): hub v0.131.0 (2026-10-04) .. v0.136.0 (d4be9f6f, 2026-10-05). The build copies website assets: felhom.eu scripts/build-hub.sh:98 `cp "${WEBSITE_ASSETS_DIR}"/*-logo.svg "${BUILD_DIR}/assets/" 2>/dev/null // true`, and the hub build workspace /mnt/5_hdd/felhom.eu/build/felhom-hub/workspace/assets/ holds radicale-logo.svg + 3 screen | 8 |
| R-768 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Grimoire is not built because upstream rules out public exposure and ships no image for v1.x; the row only watches for that to change. | 1 |
| R-769 | P4 | STILL-TRUE-NOT-SMALL | New-app idea waiting on an operator decision (a fork as new upstream, after R-767). No source to check. | 1 |
| R-770 | P4 | STILL-TRUE-NOT-SMALL | New-app idea waiting on the operator's go/no-go (CC recommends not building). No source to check. | 1 |
| R-771 | P4 | STILL-TRUE-NOT-SMALL | New-app idea waiting on the operator's go/no-go. No source to check. | 1 |
| R-779 | P4 | STILL-TRUE-NOT-SMALL | Live proof gap on the real Cloudflare tunnel needing the operator's phone off wifi; not checkable from source. | 1 |
| R-781 | P4 | STILL-TRUE-SMALL | FIX: After the clone, delete the real onboarding/<app>.md records (all but _TEMPLATE.md and the exempt wger.md the cases use) from the scratch clone, so genuine cases judge only the records they build; alternative: point the stand-in sibling at the real felhom.eu documentation tree read-only. — app-catalog@917a779 scripts/test_gate_decoys.py:498 `sh(["git", "clone", "-q", "file://" + ROOT, cat], c | 6 |
| R-786 | P4 | STILL-TRUE-NOT-SMALL | app-catalog@917a779 onboarding/sparkyfitness.md has 8 '/ open' rows, incl. :18 0.5, :20 0.7, :28 1.6, :29 1.7, :58 5.4, :68 8.3 (plus 0.1 licence = R-784, 2.1 = R-807). Most need live measurement (runtime internet, phone sign-in, second memory watch). | 3 |
| R-793 | P4 | NOT-WORTH-IT | PICK close-as-accepted: Four apps ship enterprise/BUSL code that is off as Felhom runs them; the row reminds us never to enable EE features or the -enterprise meilisearch image. | 2 |
| R-794 | P4 | STILL-TRUE-NOT-SMALL | app-catalog@917a779 seven redis 7 images: dawarich/docker-compose.yml:164 `image: redis:7.4-alpine`, docmost:86, immich:123, outline:88, nextcloud:103, paperless-ngx:125, romm:136 `image: redis:7-alpine`. Moving each needs a harness-proven ladder step (7 apps). | 3 |
| R-796 | P4 | NOT-WORTH-IT | PICK close-as-accepted: MeTube's browser/phone 'send to MeTube' helpers cannot pass the family gate; households paste links in the page. | 2 |
| R-797 | P4 | NOT-WORTH-IT | PICK close-as-accepted: CI's single-repo clone cannot check rule 3 of the family-gate gate; it says NOT CHECKED, and the pre-push hook checks it. | 2 |
| R-798 | P4 | STILL-TRUE-SMALL | FIX: Remove the dead SWAGGER_ENABLED line (or rename to API_DOCS_ENABLED=false, which v3.5.0 reads and defaults to false). No image: line moves. — app-catalog@917a779 templates/grimmory/docker-compose.yml:29 ` - SWAGGER_ENABLED=false` still present. | 2 |
| R-799 | P4 | STILL-TRUE-SMALL | FIX: Add "download_type": "video" to the POST /add body. — app-catalog@917a779 scripts/upgrade_fixtures_box.py:2183 `data=json.dumps({"url": self.URL, "quality": "best", "format": "any", "auto_start": True}), method="POST")` - no download_type. | 2 |
| R-804 | P4 | NOT-WORTH-IT | PICK close-as-accepted: plant-it's image repository does not exist; the template is already abandoned and not installable, and no box runs it. | 2 |
| R-805 | P4 | STILL-TRUE-NOT-SMALL | app-catalog-felhom.eu scripts/check-volume-persistence.py:342 'if m["class"] == "named-declared" and m.get("files", 0) == 0:' — only named volumes judged empty; binds not. Last change 917a779 (R-788). The rule change itself is small, but it flips Grimmory/komga/paperless-ngx/radarr/sonarr to UNDETERMINED and needs a live re-sweep to regenerate the verdict tables; the row also asks for a decision. | 6 |
| R-806 | P4 | STILL-TRUE-SMALL | FIX: Make routed_ports return the traefik loadbalancer.server.scheme (reuse upgrade_boxport LB_SCHEME_RE) and have the GET exercise use that scheme with curl -k for https. The gramps-web :5000 non-answer stays a separate live look (narrow the row to it). — app-catalog-felhom.eu scripts/check-volume-persistence.py:586 'code = _sh(a + [f"http://{ip}:{port}{path}"], timeout=40)' — plain http always; | 6 |
| R-807 | P4 | STILL-TRUE-NOT-SMALL | Per-app upload seeds for 13 apps (claper, crafty-controller, dawarich, docmost, gramps-web, immich, outline, sparkyfitness, tandoor, vikunja, wger, wishlist, zipline) + plex/wanderer; each needs a live fixture run. Gate rule at scripts/check-volume-persistence.py:342 still makes empty declared volumes UNDETERMINED (917a779). | 3 |
| R-814 | P4 | UNCHECKED | Live Hetzner console state (box 611421 status); not visible in source. Operator action only. | 2 |
| R-815 | P4 | UNCHECKED | First GC completion on felhom-offsite is PBS server-side live state; grep of documentation found no GC completion record (DIAG-backup-missed-2026-07-26.md:43 'prune/GC history NOT COLLECTED'). | 3 |
| R-816 | P4 | STILL-TRUE-NOT-SMALL | Needs a live exercise of six failure classes on a scratch guest; no source change can close it. | 2 |
| R-817 | P4 | STILL-TRUE-SMALL | FIX: Add a dated correction under decision 56: the swap rolls back to the image running when the swap began (controllerswap.go Swap/rollback); the kept previous image is for a hand roll-back. Do not rewrite the ruling itself. — felhom.eu documentation/architecture/09-update-architecture.md:619 '56. **A box keeps the controller image it runs and the one before it** (the self-update's roll-back targ | 8 |
| R-818 | P4 | STILL-TRUE-SMALL | FIX: Add a dated correction note under the v0.109.0 entry (and the hub CHANGELOG mentions at :695/:701) naming the real closed rows from CLOSED-ITEMS.md. Also present in felhom-controller/CHANGELOG.md:3107 and :3234 (R-330/R-331 for v0.224.0/v0.225.0) — a second repo; either note it there too or narrow the row. — felhom.eu hub/CHANGELOG.md:759 '## v0.109.0 — the Backup card told every operator tha | 6 |
| R-819 | P4 | STILL-TRUE-SMALL | FIX: Let rule 3 accept an id found in CLOSED-ITEMS.md (and check the stand's status agrees), fix the R-273/R-356 dangling ids, register the gate in repo_gates.py with a decoy. — Ran python3 scripts/check_stands.py (read-only): still convicts e.g. 'fail.stolen-machine: register id R-281 is not in OPEN-ITEMS.md', 'fail.customer-self-restore: register id R-356 ...'. grep 'stands' in scripts/repo_gate | 6 |
| R-832 | P4 | STILL-TRUE-NOT-SMALL | Deferred roadmap item: a third-location copy of ep0 is money + operator decision (decision 71). Nothing in source to change. | 1 |
| R-844 | P4 | STILL-TRUE-NOT-SMALL | Needs a household timeline on the controller, which does not exist (row: 'when the box gets a household timeline'). A new surface, not a fix. | 2 |
| R-855 | P4 | STILL-TRUE-SMALL | FIX: Add a small helper (e.g. osSvc.DockerNightsEffective()) mapping negative→0 and 0→2, and print that in the start log. — felhom.eu hub/cmd/hub/main.go:450 'logger.Printf("[INFO] osupdates: the Docker engine set is approved only by the operator, after %d healthy ring-0 night(s)", osSvc.DockerNights)' prints the raw value; internal/osupdates/service.go:169 'negative means none'. | 4 |
| R-856 | P4 | STILL-TRUE-NOT-SMALL | Row is marked an operator design question (crash-restart suppression for app mails); not a defect yet. | 1 |
| R-857 | P4 | STILL-TRUE-SMALL | FIX: Have newest_baked accept an optional suffix after the date and, for equal versions, prefer the newest bake-log timestamp (or refuse two dirs for one version); add 're-vouch at once after a same-version re-bake' to the runbook. — felhom.eu scripts/golden_currency_gate.py:146 'EVIDENCE_RE = re.compile(r"^golden-(\d+)\.(\d+)\.(\d+)-\d{4}-\d{2}-\d{2}$")' and :237-238 'found.sort() / return found[ | 7 |
| R-878 | P4 | STILL-TRUE-NOT-SMALL | Next action is 'measure a large volume first' — a live measurement; the fix direction is a behaviour change to the catch-up. | 2 |
| R-881 | P4 | STILL-TRUE-SMALL | FIX: Add an rm -f of /usr/local/sbin/felhom-priv-apply to the uninstall step (tolerate-absent, like felhom-pbs-apply at :1161) and fix the :1679 comment; ships at the next installer tag. — felhom.eu scripts/felhom-host-install.sh: grep 'felhom-priv-apply' → no hit anywhere in the installer (uninstall does not remove it); :1679 '+ guest-hook snippet under /var/lib/vz/snippets/ (agent-installed at r | 4 |
| R-884 | P4 | UNCHECKED | Live ArgoCD diff on DooPlex (forbidden to touch here). Related: homelab-manifests mon-system/monitoring.yaml:399-426 is the same prometheus Deployment R-211 concerns. | 2 |
| R-885 | P4 | STILL-TRUE-SMALL | FIX: Finish and push the in-progress script_tests_gate.py (walks scripts/ for test_*.py, exit-code verdict, nesting guard) registered in repo_gates.py; coordinate with the session that owns the dirty tree. — On main (b018ca90) scripts/repo_gates.py runs no test_*.py suite. NOTE: the felhom.eu working tree holds UNCOMMITTED work for exactly this row by another session: '?? scripts/script_tests_gate | 6 |
| R-30 | P3 | STILL-TRUE-NOT-SMALL | Design change (presence from the Dir-2 long-poll instead of the report clock), size M; no commit with R-30 after aa9c08f0 (filing). | 3 |
| R-31 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu hub/internal/web/configs.go:1594 'd, err := s.offsite.ProvisionOffsite(ctx, cfg.CustomerID, in)' still in-request; :1584 detaches from the request context (mid-cancel fixed) but no async/status card. | 5 |
| R-35 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller controller/internal/report/config_refresh.go:65 'config-refresh: applied config_version=%d — self-restarting to load it'; sessions are in-memory only: controller/internal/web/auth.go:259-260 's.sessions[token] = &session{'. Hot-apply or persisted sessions is a design change with security weight. | 6 |
| R-49 | P3 | STILL-TRUE-NOT-SMALL | app-catalog-felhom.eu templates/immich/.felhom.yml:28-34 backup block has no cache/volume exclusion; row itself says a capture-set exclusion needs its own ruling (data-loss-shaped). | 4 |
| R-50b | P3 | FIXED-BY-LATER-WORK | Claim 'fetched via fetch_raw from raw/branch/main — no tag, no pin' no longer true: bee68484 (installer v1.23.0, R-110/R-183) pinned fetch_raw to the vouched agent tag — felhom.eu scripts/felhom-host-install.sh:533 '"$GITEA_BASE/$GITEA_OWNER/$AGENT_REPO/raw/tag/v$ART_AGENT_VER/$path" \' (leg b). Leg (c)-like signed delivery: felhom-agent c9fa2e7 (R-840 config bundle) and configs/test_felhom_config | 7 |
| R-78 | P3 | STILL-TRUE-NOT-SMALL | An owed operator decision + spike (local_api authority); not a code defect. | 1 |
| R-79 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller controller/internal/monitor/healthcheck.go:100 'fmt.Sprintf("SSD disk usage critical: %.0f%%"', :213 'Protected container not running: %s'; rendered raw at controller/internal/web/alerts.go:241 'Message: issue, // ON THE WIRE ... not ours to translate; slice 3'. Whole-surface, seam needs a spike. | 5 |
| R-118 | P3 | STILL-TRUE-SMALL | FIX: Only statfs the mount when s.devicePresent(d.MountPath) is true (else leave capacity zero/unknown); put statfsCapacity behind a seam var for the test. — felhom-agent internal/localapi/disks.go:401 'if total, used, okc := statfsCapacity(d.MountPath); okc {' — no device-presence guard on the union path; devicePresent exists (disks.go:985) and is used just above (:381) for BoundUnderParent. | 6 |
| R-121 | P3 | FIXED-BY-LATER-WORK | 3d7a2761 (hub v0.135.0, R-530/R-604 'boxes left behind listed and alarmed'): felhom.eu hub/internal/osupdates/service.go:79 'EventAgentBehind = "agent_behind" // warning, operator' with :180 'AgentBehindAfter: a box runs an agent older than the vouched one this long → an operator alarm' (7 d window, the staleness window the row asked for). | 5 |
| R-126 | P3 | STILL-TRUE-SMALL | FIX: Skip IsNetwork() paths in the export-destination list and refuse them in isValidDrivePath for the export POST (keep scanning for import if wanted, via a separate list). — felhom-controller controller/internal/web/handler_export.go:377-386 storageDriveList() appends every s.settings.GetStoragePaths() entry with no IsNetwork() filter; the predicate exists at controller/internal/settings/setting | 6 |
| R-127 | P3 | STILL-TRUE-NOT-SMALL | Leg (a) still true: grep 'data_key: true' in app-catalog-felhom.eu templates → only adventurelog, dawarich, homebox, papra, sparkyfitness; n8n N8N_ENCRYPTION_KEY, wanderer POCKETBASE_ENCRYPTION_KEY, calcom CALENDSO_ENCRYPTION_KEY, bookstack APP_KEY unflagged (templates/n8n/.felhom.yml:38 etc.). Leg (b) (regenerated DB password vs restored PGDATA) needs a design choice. Leg (a) alone is a ~45-min c | 6 |
| R-130 | P3 | STILL-TRUE-SMALL | FIX: Take the cheap honest branch: rename to RECOMMENDED_MIN_LVM_GIB and reword the warning to 'below the recommended …' (making it refuse would change install behaviour and needs a ruling). — felhom.eu scripts/felhom-host-install.sh:348 'HARD_MIN_LVM_GIB=120 # a useful appliance won't fit below this on local-lvm' and :1760 '... // log_warn "local-lvm free ~${free_gib} GiB < hard min ${HARD_MIN_ | 4 |
| R-132 | P3 | UNCHECKED | Whether HUB_PW was rotated is out-of-band operator state; not visible in source. | 1 |
| R-136 | P3 | STILL-TRUE-SMALL | FIX: Introduce a const sessionCookieName = "__Host-hub_session" and use it at all five sites (Path=/, Secure, no Domain already hold). Every operator logs in once more; plain-HTTP browser access stops (Basic auth unaffected). — felhom.eu hub/internal/web/server.go:857 'Name: "hub_session",' and readers at server.go:806, :896, :918 and apps.go:351. | 4 |
| R-137 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller controller/internal/cloudflare/waf.go:18 'globalRuleDesc = "[felhom-geo] Global"', :21 'appRuleDescPrefix = "[felhom-geo] app:"' — still not namespaced. Two-repo M change. | 3 |
| R-138 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller controller/internal/infra/infra.go:157-159 writes CF_DNS_API_TOKEN when d.CFAPIToken != ""; no shared-zone guard in hub (grep shared.zone: none). Needs a policy decision first; no shared zone exists today. | 4 |
| R-179 | P3 | STILL-TRUE-SMALL | FIX: In uninstall step 4c, before the umount loop: stop+disable every mnt-felhom\x2ddrives-*.automount/.mount unit, rm their files from /etc/systemd/system, daemon-reload (tolerate-absent; still never umount -l/-f). — felhom.eu scripts/felhom-host-install.sh uninstall section :1121-1157 handles felhom-shared-parent and umounts under /mnt/felhom-drives, but grep 'x2ddrives/automount' → no hit: the | 6 |
| R-180 | P3 | STILL-TRUE-SMALL | FIX: In the same pre-flight block, die (byo) / die (appliance) if ARCHIVE_STORAGE is not in PVE_STORAGES, with a message naming --acl-storages. — felhom.eu scripts/felhom-host-install.sh:1800 'if pvesm status --storage "$ARCHIVE_STORAGE" ...' checks existence only; PVE_STORAGES=(local local-lvm felhom-pbs) at :322; no ARCHIVE_STORAGE ∈ PVE_STORAGES assertion. | 5 |
| R-190 | P3 | NOT-WORTH-IT | PICK close-as-accepted: A storage permission vanished once on demo-felhom in August and nobody knows why. Since agent 0.124.1 the agent puts it back by itself and mails the operator when it happens. | 4 |
| R-200 | P3 | FIXED-BY-LATER-WORK | The remaining half (customer-facing recovery-code form: yell → R form → preview) shipped as the recovery screen: felhom-controller 636c51e 'R-193: the recovery screen — unlocking, and only unlocking (v0.200.0)'; controller/internal/web/templates/recovery.html:82 '<form id="unlock-form" method="POST" action="/recovery/unlock" autocomplete="off">', routed at internal/web/server.go:602. Plumbing half | 8 |
| R-211 | P3 | STILL-TRUE-NOT-SMALL | homelab-manifests (/home/kisfenyo/git/homelab-manifests @87dfc29) mon-system/monitoring.yaml:420 'image: prom/prometheus:v3.15.0', :426 '--web.enable-lifecycle'; grep 'reload/checksum/config' → none. The manifest edit is small, but it rolls the production Prometheus on DooPlex (operator territory) and the same Deployment is OutOfSync per R-884 — do the two together. | 6 |
| R-231 | P3 | STILL-TRUE-NOT-SMALL | Owner operator; DooPlex /opt/backup/scripts remains host state. Partially touched by cea8502f (scripts/hub-db-backup versioned in felhom.eu, cites R-231) but that covers only the hub-DB push, not /opt/backup/scripts or the same-disk/no-off-site facts. | 4 |
| R-235 | P3 | FIXED-BY-LATER-WORK | felhom.eu c033b3b6 'ISO 1.28.0 source: the console stops showing the pairing code once bound (R-535)'. scripts/iso/felhom-bootstrap.sh:538: `print_bound_banner # R-535: replace the pairing code on the console with the truth`. Same defect already CLOSED twice in CLOSED-ITEMS.md as R-535 (line 232) and R-214 (line 200, 'proven on a fresh install'). | 4 |
| R-240 | P3 | STILL-TRUE-SMALL | FIX: Replace the producer string at offbox.go:1168 with wording that drops 'Sikeres' but keeps the lowercase marker substring, e.g. 'Ez a futás semmit nem mentett: nincs mentésre jelölt alkalmazás'; update the tests that pin the literal. — felhom-controller/controller/internal/backup/offbox.go:1168: `warns = append(warns, "Sikeres — nincs mentésre jelölt alkalmazás")`; web/handlers.go:1068-1069 re | 8 |
| R-242 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/scripts/golden_currency_gate.py:23-24: 'It does **NOT** check that the golden was **VOUCHED**, because the vouched version lives ONLY in the hub's `hub_settings` table'; :40 'That vouch half is STILL open after 2026-09-13'. Last gate commits 5ef0f52b/ae59c31a did not add a vouch check. | 5 |
| R-244 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/hub: no cascade leg touches app_log_issues — only writers are internal/store/telemetry.go:176-209 (upsert), :537 `DELETE FROM app_log_issues WHERE last_seen < ?`, :556/:575 operator deletes by app/id. cmd/hub/main.go:1004: `if n, err := s.PruneStaleIssues(time.Now().Add(-30 * 24 * time.Hour))` (since a757bee0, hub v0.4.0). | 7 |
| R-250 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/hub/internal/offsite/offsite.go:71-72: `var defaultScanBackoff = []time.Duration{2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second, 30 * time.Second}`; scanner.go:40 dials plain "tcp" (no A-record preference); no R-250 commit. | 7 |
| R-251 | P3 | STILL-TRUE-SMALL | FIX: In offsiteNewestPerTag skip the 'felhom-offbox' marker tag (move the constant into package backup and reuse it from web); consider whether '_shares' should render as a named row or be skipped. — felhom-controller/controller/internal/backup/offbox_inventory.go:102-108: loops `for _, tag := range sn.Tags` and adds every non-empty tag to `newest` — no filter for 'felhom-offbox'. The marker filte | 9 |
| R-255 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller: still no page-wide runtime secret-sentinel test — `grep -l sentinel internal/web/*_test.go` hits only edge_safe_status_test.go, i18n_parity_test.go, recovery_test.go; controller/scripts/secret_in_markup_gate.py remains the only all-template net. No commit cites R-255. | 7 |
| R-257 | P3 | STILL-TRUE-SMALL | FIX: Rewrite flash.offbox.not_orphaned (hu + en) to say what the customer tried, that it does not apply now, and where to look, without 'offsite'/'elárvult', e.g. 'A távoli mentés rendben van, nincs mit félretenni. Ha gondod van vele, írj nekünk.'; wording sign-off from the operator (owner Viktor). — felhom-controller/controller/internal/i18n/locales/hu.json:1409: `"flash.offbox.not_orphaned": "Az | 5 |
| R-262 | P3 | STILL-TRUE-SMALL | FIX: One-repo fix: narrow the comment to say hostBackup is field-for-field and hostRestoreTest is a deliberate SUBSET (lists mount_parity/mount_inventory as not modelled), and add a hub test that names the two agent fields as known-unmodelled so a future addition must edit it. Adding the fields + fixture is a two-repo change (byte-identical golden) and stays a separate choice. — felhom.eu/hub/inte | 7 |
| R-269 | P3 | STILL-TRUE-SMALL | FIX: On a map hit, also stat the store and reload when its size differs from loadedSize before answering (one cheap stat per auth), so a rotated-out token is rejected without depending on an unrelated miss. — felhom-agent/internal/localapi/tokenstore.go:173-176: `if vmid, ok := s.byHash[want]; ok { if subtle.ConstantTimeCompare(...) == 1 { return vmid, true } }` — a superseded token's hash is stil | 6 |
| R-270 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/internal/bootstrap/bootstrap.go:255: `if cfg == nil // cfg.LocalAPI.Endpoint != "" {` (fill-only, never refreshes); DetectEndpointDrift (bootstrap.go:369-399) compares only the endpoint. No R-270 commit. | 5 |
| R-271 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/internal/channelhealth/checker.go:152: `if prev != "" && prev != "up" {` (unseeded->up is silent); checker.go:87 alert text still says '(re-bootstrap)'. No R-271 commit. | 4 |
| R-274 | P3 | FIXED-BY-LATER-WORK | felhom.eu eb600872 'R-297: installer compares a local golden against the manifest before using it'. scripts/felhom-host-install.sh:3074: `if golden_local_matches_manifest "$GOLDEN_VOLID"; then` (digest vs ART_GOLDEN_SHA, else baked marker vs ART_GOLDEN_VER; otherwise ignores the local golden and fetches, or dies if the operator named it, :3080). Line numbers differ from the triage note (2855-2905) | 5 |
| R-275 | P3 | STILL-TRUE-SMALL | FIX: Purge every sibling `"${agent_cfg}".*` (and then rmdir/rm the config dir) instead of `.bak*`; fix the WIPED line wording. — felhom.eu/scripts/felhom-host-install.sh:1059: `for _cfgbak in "${agent_cfg}".bak*; do [[ -e "$_cfgbak" ]] && run rm -f "$_cfgbak"; done` — glob still misses agent.json.campaign8-before, .campaign9-prev, .pre-e-target-move, .pre-prunegate.bak; :814 still claims 'config ( | 6 |
| R-276 | P3 | STILL-TRUE-SMALL | FIX: In run_uninstall (full scope): stop+disable wg-quick@wg-felhom and remove /etc/wireguard/wg-felhom.conf via run(); add it to WIPED, and add a KEPT line 'the hub-side WireGuard peer registration — remove it in the operator UI'. — felhom.eu/scripts/felhom-host-install.sh: no reference to wg-felhom/wg-quick anywhere (grep 'wg-quick\/wg-felhom' empty); _uninstall_statement (:807-845) lists neithe | 6 |
| R-277 | P3 | STILL-TRUE-SMALL | FIX: For UsageStr use fmtBytesAuto when the usage is below 1 GB (keep GB for the quota and the bar), so a non-empty repo never renders as 0.0 GB. — Part (a) FIXED by felhom.eu f5c9411e 'R-331 (hub half): the Backup card reads `offsite`, not the dead `backup` fields (v0.109.0)' (hub/internal/web/backup_card.go uses fmtBytesAuto :135-142). Part (b) still true: hub/internal/web/offsite_box.go:54 `ret | 8 |
| R-282 | P3 | FIXED-BY-LATER-WORK | felhom.eu 4d6ec7c 'hub v0.104.0: ... the hub half of the naming (R-295)' + controller v0.211.0 (R-295 CLOSED, CLOSED-ITEMS.md:207) + R-323 hub v0.105.0. hub/internal/notify/templates.go:204: '// R-295, HUB HALF (2026-08-13). ONE NAME PER SECRET, and it is „Beállító kód".'; hub/internal/claim/engine.go:51 `EmailReenroll EmailKind = "reenroll"` (mail names the setup page a rebuilt box shows). | 5 |
| R-283 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/hub/internal/claim/engine.go:7: '// engine: a rotation bumps the generation (single active code) and NEVER clears claimed_at.'; ReissueForReenroll (engine.go:195-212) rotates and mails but leaves the claim set — the hub still shows the customer as claimed after a guest rebuild. No R-283 commit. | 5 |
| R-298 | P3 | UNCHECKED | The template gate is still there: felhom-controller/controller/internal/web/templates/storage.html:364 `if(d.role==='user-data'){` else protected (:368). BUT the agent no longer reclassifies a backup-target drive: felhom-agent/internal/localapi/disks.go:1230-1236 ('WHY THIS IS NOT A ROLE RECLASSIFICATION ... the drive that now holds the whole-guest archives is ALSO the enrolled user-data drive') a | 10 |
| R-306 | P3 | STILL-TRUE-SMALL | FIX: Make _state_put (and _state_mark) return 0 when PREFLIGHT_ONLY is true, so a preflight-only run writes nothing; the real run's preflight records ownership again. — felhom.eu/scripts/felhom-host-install.sh:418: `$DRY_RUN && return 0` (only DRY_RUN short-circuits _state_put); :1851/:1854 `_state_put dnsmasq_preexisting yes/no` run unguarded in preflight, while :226 says '--preflight-only: ... n | 6 |
| R-314 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller: StopAbandon is called only from cmd/controller/main.go:244 (CLI) — `grep -rn StopAbandon` shows internal/backup/offbox_abandon.go:374 and that CLI call, no web handler. | 4 |
| R-317 | P3 | STILL-TRUE-SMALL | FIX: Probe the dnsmasq unit (e.g. /usr/lib/systemd/system/dnsmasq.service or /lib/systemd/system/dnsmasq.service) behind a small stat seam instead of /usr/sbin/dnsmasq. — felhom-agent/internal/lanresolver/lanresolver.go:107: `if _, err := os.Stat("/usr/sbin/dnsmasq"); err != nil { // metadata read, no privilege needed` — still probes the dnsmasq-base file. No R-317 commit. | 4 |
| R-330 | P3 | STILL-TRUE-NOT-SMALL | felhom-agent/internal/hub/report.go:406-425 SmartSummary carries reallocated/pending/offline_uncorrectable + NVMe set only — no 187/188/199 fields. Wire change across agent + hub (+ controller), declared M. | 3 |
| R-332 | P3 | STILL-TRUE-NOT-SMALL | Closing condition is live-only (a real degrading disk or an injection through agent /disks -> controller -> hub). Last related commits ea16a21b/2fa1efc narrowed the restart half only; no commit records a live Hiba-from-counters verdict. | 3 |
| R-333 | P3 | STILL-TRUE-NOT-SMALL | (b) felhom-agent/internal/storage/hostops.go:374: `out, stderr, err := h.runner.Run(ctx, h.bins.Smartctl, "-a", "-j", device)` — no -n standby. (a) still an operator decision (Viktor decides). | 5 |
| R-338 | P3 | UNCHECKED | felhom.eu/documentation/operations/nodes.md:86-88 now says demo-hp 'Agent config shape (R-50 island): local_api on 169.254.253.1:8443/vmbr9, guest eth1 169.254.253.2/30', and :84 records demo-hp was reprovisioned (address 192.168.0.87 -> 192.168.0.104, read 2026-09-21). Whether the reprovisioned box is actually on the island is a live-box fact (agent.json, pct config) not provable from source. | 5 |
| R-340 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/scripts/felhom-tenantsync.sh: no health op and no 8007 probe (grep '8007\/health)' empty). Needs an ep0 (protected) script version bump + hub signal (M). | 3 |
| R-349 | P3 | STILL-TRUE-NOT-SMALL | felhom-agent/internal/hub/report.go:282-292 reports only WrapperSHA256; no agent binary sha field in the report (grep AgentSHA256 finds only the hub's manifest entry, hub/internal/api/handler.go:2726). R-349 commits 40d857b/910fd911 are the manual correction only. | 4 |
| R-350 | P3 | UNCHECKED | Whether the hub operator password was rotated after 2026-08-20 lives only in the hub DB / operator; git log shows no rotation record (only 910fd911 filing it). Not determinable from source. | 3 |
| R-362 | P3 | STILL-TRUE-SMALL | FIX: When MkdirAll/write of the restore destination fails with EACCES/ENOENT, check whether the destination's drive is disconnected/decommissioned or no longer a mountpoint, and return a Hungarian error naming the drive instead of the raw permission error. — felhom-controller/controller/internal/backup/offbox_restore.go:380, offbox.go:1929, shares_restore.go:116: `return fmt.Errorf("restore dir: % | 6 |
| R-363 | P3 | STILL-TRUE-SMALL | FIX: Replace sched.Daily("fill-watch", "03:30", …) with sched.Every("fill-watch", time.Hour, …) (scheduler.go:104), keep the startup check. — felhom-controller/controller/cmd/controller/main.go:1546: `sched.Daily("fill-watch", "03:30", func(ctx context.Context) error { return fillWatcher.Check() })`. Watcher emits on escalation only with a persisted band (internal/fillwatch/fillwatch.go:133, :231) | 6 |
| R-388 | P3 | STILL-TRUE-NOT-SMALL | Product direction, operator's call; recorded as [DESIGN — DIRECTION] in documentation/architecture/08-alarm-ladder.md §8 per the row. Nothing in source to fix. | 2 |
| R-401 | P3 | STILL-TRUE-NOT-SMALL | Event-triggered watch row: felhom-controller/controller/internal/backup/offbox_integrity.go:63 `const integrityCheckTimeout = 30 * time.Minute`, :78 `var integritySlowNoticeThreshold = 5 * time.Minute` — unchanged; the trigger (slow WARN on a large store) has not been recorded. | 3 |
| R-409 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller/controller/internal/backup/recovery_unit.go:58 `Checksums map[string]string json:"checksums" // sha256 of captured compose/ files`; writers at :147, :152, :202 hash only compose/.felhom.yml/app.yaml — no db_dumps or volume_dumps hash. | 4 |
| R-412 | P3 | NOT-WORTH-IT | PICK close-as-accepted: A narrow race: if a recovery unit is destroyed inside an off-site run after its own dump leg, the push ships the just-rebuilt hollow unit; the next run repairs it and the WARN line now says it carried no data. | 4 |
| R-433 | P3 | STILL-TRUE-NOT-SMALL | Hetzner answered (ticket per the row): file-level snapshot access is a MAIN-account capability. hub/internal/hetznerapi/hetznerapi.go still has no snapshot read method (only size_snapshots usage, :83-87). The owed proof (read a file from a snapshot with the main account; forced-command append-only key) is a live, credential-bound operator act. | 4 |
| R-435 | P3 | STILL-TRUE-NOT-SMALL | felhom.eu/hub/internal/monitor/offsite.go:255: `snapshotDropFraction = 0.5 // more than half the history gone in one step` — no per-tag second signal exists. | 4 |
| R-440 | P3 | STILL-TRUE-NOT-SMALL | app-catalog-felhom.eu @917a779: 15 templates still have no `update_ladder:` in .felhom.yml — bentopdf code-server glance gokapi gramps-web homebox homepage jellyfin onlyoffice plant-it plex recipe-importer seerr vaultwarden wanderer (calibre-web got its first step in 53a4a1d). For these, pins float with no recorded digest. | 8 |
| R-444 | P3 | STILL-TRUE-NOT-SMALL | No fstrim anywhere in felhom-agent or felhom.eu/scripts (grep 'fstrim' over *.go/*.sh empty). Needs a new periodic host job (agent, privileged, sudoers/bundle) and possibly an operator surface. | 3 |
| R-446 | P3 | DUPLICATE | of R-440 — felhom-controller/controller/internal/stacks/updateorder.go:96: `if len(s.CatalogDigests) == 0 // s.CatalogTestedAt.IsZero() { return false }` — blind only for apps with no ladder entry, i.e. the same 15 templates R-440 lists (app-catalog has no update_ladder for them). Both rows close by the same act: each app's first proven ladder step (R-462). | 6 |
| R-450 | P3 | FIXED-BY-LATER-WORK | The row's only remainder was 'the other ten PostgreSQL apps need two-venue proof'. R-463 CLOSED 2026-09-30 by felhom.eu 25cb3eb9 ('The last six PostgreSQL apps decided'): 8 of 11 moved by the box's own conversion, 3 (zipline, adventurelog, immich) stay by decision 42; CLOSED-ITEMS.md:223. Source proof: app-catalog templates/docmost/docker-compose.yml:62 'image: postgres:18-alpine' (also rallly:67, | 8 |
| R-458 | P3 | NOT-WORTH-IT | PICK close-as-accepted: A frozen (pinned-behind) app can receive a newer health check from .felhom.yml. The only result is a false 'degraded'/dead-app alarm, never data loss, and only for type: api probes with an expect block. | 12 |
| R-462 | P3 | STILL-TRUE-NOT-SMALL | Ongoing multi-session work (fixtures and ladders per app). Last progress: catalog e6f3ec2 (2026-09-30), audits/more-night-apps-2026-09-30/. 21 apps still have no ladder (per the row). Not checkable as done from source. | 3 |
| R-468 | P3 | STILL-TRUE-NOT-SMALL | A standing pre-customer arrangement, not a defect. The mechanism is live: felhom.eu/scripts/golden_currency_gate.py:137 reads documentation/tests/golden-waiver.yml. The waiver file is currently ABSENT: deleted in felhom.eu 5efe6dae (2026-10-04, 'golden 0.292.0 vouched ... golden waiver deleted'). The row retires only at the first external install, so it stays as a watch. | 4 |
| R-469 | P3 | STILL-TRUE-SMALL | FIX: Reword the rule heading at CLAUDE.md:103 and the 'What is NOT lifted' paragraph (:112-114) to say that PostgreSQL crosses a major one app at a time with engine_conversion + both-venue proof (decision 35) and that MySQL stays refused. Then close R-469 citing R-463's closure. Do not delete the gate: it is now the per-app enforcement. — What stood between this row and its close was R-463, now CL | 8 |
| R-489 | P3 | FIXED-BY-LATER-WORK | The residual (a unit-restore-recreated volume has no compose label, so the remove answered []) was fixed in felhom-controller 206b035 (v0.268.0, R-658). controller/internal/stacks/delete.go:1089-1090: '// appVolumeSet is every volume the removal accounts for: the ones carrying the project label AND the // ones the app's definition declares that Docker holds by name (R-658, v0.268.0).' delete.go:70 | 5 |
| R-498 | P3 | STILL-TRUE-SMALL | FIX: When building the app info view, rewrite each first_steps entry: replace '<word>.DOMAIN' with the stack's real address (installed SUBDOMAIN + customer domain when installed, else the template default subdomain + customer domain). Use the same approach as known_login.go:67. — Still literal: grep -l '\.DOMAIN' over app-catalog templates/*/.felhom.yml -> 58 of 58; e.g. templates/bookstack/.felho | 8 |
| R-516 | P3 | STILL-TRUE-NOT-SMALL | By its own text it now waits for a Hungarian walk on a box with a second drive (items 4, 7, 8, 9, 10) and needs a separate row for item 11. That is live-box work, not source. No commit after 2026-09-20 names R-516 as closed. | 3 |
| R-521 | P3 | STILL-TRUE-NOT-SMALL | No storage-disconnect suppression of app_start_failed: controller/cmd/controller/main.go:2796 'down := (stacks.IsDownState(st.State) // crashLooping) && !userStopped && !quiesced[st.Name]' (only user-stop/quiesce suppress), and controller/internal/notify/notifier.go:718 emits app_start_failed per newly-down app. It also needs the hub cooldown semantics changed (per-key cooldown outliving storage_r | 6 |
| R-522 | P3 | STILL-TRUE-NOT-SMALL | No tunnel-connection signal in the controller: grep for TunnelConnected/cloudflared connection state in controller/internal -> none. The tile comes from container metadata: controller/internal/web/inframeta.go:21-22 '"cloudflared": { DisplayName: "Cloudflare Tunnel"'. Fixing it needs a new state source (cloudflared metrics or the hub-push result) wired into the tile, plus a live internet-cut valid | 5 |
| R-531 | P3 | STILL-TRUE-NOT-SMALL | Both measurements are done (audits/evidence-drill-0243-2026-09-16/). What remains is an operator design question about the crash-loop budget (a slow loop every 20 min is never paused). That is an operator decision, not code. | 2 |
| R-540 | P3 | STILL-TRUE-NOT-SMALL | Still a single pool box: felhom.eu/hub/cmd/hub/main.go:367 'poolBoxID, _ := strconv.ParseInt(os.Getenv("HETZNER_POOL_BOX_ID"), 10, 64)'. It needs a selection-rule design and eventually a second box (money). No risk today (0.3 % full per the row). | 3 |
| R-542 | P3 | UNCHECKED | The controller passes the agent's 'initialize' list through untouched: controller/internal/web/agent_disk_handlers.go:160-162 mergeAttachCandidates only appends to Attach. The agent puts every candidate under initialize: felhom-agent internal/localapi/disks.go:438 'initialize = append(initialize, c) // every unclaimed disk can be initialized'. Whether a REGISTERED in-guest drive still counts as 'u | 8 |
| R-545 | P3 | STILL-TRUE-NOT-SMALL | Still no un-configure route: controller/internal/web/server.go:744-783 lists /backup/offbox/{config,toggle,enable-all,offer-dismiss,run,reset,status,restore,place,reconstitute,verify-copy/delete,confirm-escrow,inject-password}; nothing removes a target. The fix is a new destructive customer action (shred data/offbox/) with a hub-escrow refusal check (R-241 rule), Hungarian/English copy and a UI. T | 4 |
| R-547 | P3 | STILL-TRUE-SMALL | FIX: Also schedule the fill-watch on an interval (e.g. sched.Every("fill-watch-fast", 15*time.Minute, ...) calling the same fillWatcher.Check) next to the daily run, and log the cadence. Alternative if the operator prefers: state in 08-alarm-ladder.md that a transient full disk is out of scope. — Still daily: controller/cmd/controller/main.go:1546 'sched.Daily("fill-watch", "03:30", func(ctx conte | 8 |
| R-548 | P3 | STILL-TRUE-NOT-SMALL | The row's original fix shape SHIPPED: felhom-agent 0722b2c (2026-09-24, R-685) checks before start, internal/backup/runner.go:279 '... so a new one needs about %s (old archives are removed only after a successful backup)'. Shown in the UI by controller 44ae4de (v0.272.0). The 2026-09-30 addendum is still true and is the open part: the local tier is refused for ever (10 refusals on demo-hp) because | 6 |
| R-552 | P3 | STILL-TRUE-SMALL | FIX: Add Manager.ClearInterruptedRestore(stack) (delete from opInterrupted + persistRestoreRecordLocked under m.mu). Call it in removeStack next to ClearUpdateHold, and log when it cleared something. — The interrupted-restore notice is cleared only at controller/internal/backup/opstatus.go:61 'delete(m.opInterrupted, stack)' inside BeginRestoreOp. removeStack (controller/internal/api/router.go:955 | 6 |
| R-554 | P3 | STILL-TRUE-NOT-SMALL | Still present: controller/internal/setup/ (setup.go, handlers.go, csrf.go, network.go, templates/), and controller/cmd/controller/main.go:328-330 'if setup.NeedsSetup(cfg) { ... runSetupMode(cfg, logger)'. The fix deletes a package and adds a new waiting page (HU+EN copy) with a red-proof. It also needs a check of drill/golden reliance on .needs-setup (controller/internal/web/handler_debug.go refe | 5 |
| R-562 | P3 | STILL-TRUE-NOT-SMALL | Needs an operator word on the Hungarian number/date format (the row says so) and a deliberate Hungarian-byte change release with parity re-capture. Not checked further. | 2 |
| R-565 | P3 | STILL-TRUE-SMALL | FIX: Add an ASCII Hungarian word regex to TestI18nEnglishPages (seeded from i18n_extract.py ASCII_HU plus the words releases B/C found: mp, db, FIGYELEM, jelenlegi, majd a(z), Konfig, Megtartva, helyi, Befejezve, automatikus, kedd/szerda/szombat, szint), applied after the data mask. — The English page test detects only accented letters: controller/internal/web/i18n_parity_test.go:563 'func huLette | 6 |
| R-573 | P3 | FIXED-BY-LATER-WORK | felhom-controller 7c4a33b (v0.258.0, 'the last four Hungarian things an English household met ... R-573 the two channel banners'). controller/internal/web/alerts.go:113 'func (am *AlertManager) SetAgentChannelAlert(down bool, msgKey, msg string) {' and :136 'func (am *AlertManager) SetEndpointDriftAlert(drift bool, msgKey, msg string) {'. Both set MessageKey with msg only as a fail-open fallback. | 4 |
| R-575 | P3 | STILL-TRUE-SMALL | FIX: Make memoryVerdict return (refusal error, warningKey string, warningArgs []any) instead of a msgHU string, and render the warning at the deploy answer with the request's language (the Alert/UpdateRefusal pattern). — Still a plain string: controller/internal/stacks/deploy.go:1456 'func (m *Manager) memoryVerdict(newReqMB, newLimitMB, releasedReqMB, releasedLimitMB int) (refusal error, warning | 5 |
| R-578 | P3 | STILL-TRUE-NOT-SMALL | The lock-reentrancy guard is still one test in one package: controller/internal/backup/offsite_diag_test.go:190 'TestNoteHelpersAreNotCalledUnderTheSettingsLock'. No other package has one, and there is no gate (grep for R-578 in controller -> none). The fix needs a cross-package AST gate that knows which methods read settings (or a re-entrant read path in Settings). That is a new mechanism, likely | 5 |
| R-581 | P3 | STILL-TRUE-SMALL | FIX: In GetCustomers, join on MAX(id) per customer_id instead of MAX(received_at), and add ', id DESC' to the ORDER BY at store.go:1393 and :1446. — Still no tie-break: felhom.eu/hub/internal/store/store.go:1329 'SELECT customer_id, MAX(received_at) as max_time' joined on 'r.received_at = latest.max_time' (:1333). A same-second tie returns BOTH rows (a duplicate customer in GetCustomers), not just | 6 |
| R-584 | P3 | NOT-WORTH-IT | PICK close-as-accepted: Helper scripts with the shared DEMO controller password inline were left in a demo guest's /tmp. The cleanup rule exists, but no mechanism enforces it. | 5 |
| R-585 | P3 | STILL-TRUE-NOT-SMALL | Still finished Hungarian from callers: controller/internal/notify/notifier.go:408 'n.PushEvent("backup_failed", "error", message, BackupDetails{Error: errMsg})', :487 offbox_enlarge_blocked, :497 db_dump_failed. None of the six types is in convertedProducers (controller/internal/notify/message_customer_test.go:39). The hub still has no customerMessages entry for offbox_enlarge_blocked (felhom.eu/h | 5 |
| R-586 | P3 | NOT-WORTH-IT | PICK close-as-accepted: The ISO bootstrap harness runs only at each ISO release gate (G16), not on every push. | 6 |
| R-587 | P3 | STILL-TRUE-SMALL | FIX: At the start of build-felhom-iso.sh, refuse (non-zero exit, named reason) when any *.rootpw.txt exists in the output dir. In the SKILL publish block, add a pre-check line that aborts the rclone copy if a *.rootpw.txt is present in the publish source. — The files are gone (ls /mnt/5_hdd/felhom.eu/felhom-iso/out / grep -c rootpw -> 0). The guard is still not built: the publish still relies on t | 6 |
| R-593 | P3 | STILL-TRUE-SMALL | FIX: Move 'Az alkalmazás aldomainje' to SUBDOMAIN, give AUTH_SECRET its own description (e.g. 'A munkamenetek aláírásához használt kulcs — ne generáld újra'), add the two English i18n.en descriptions, and re-capture papra's entries in copy_freeze/hu.json with the reason in the commit. — Still wrong: app-catalog-felhom.eu/templates/papra/.felhom.yml:47 ' description: "Az alkalmazás aldomainje"' | 5 |
| R-600 | P3 | STILL-TRUE-SMALL | FIX: Call the wgsync reconciler's Trigger() before the COMPLETE log line (nil-safe), and make the line say 'wg peer removal pushed' or 'queued for the next wgsync push' depending on whether a syncer is wired. — Log line unchanged: felhom.eu/hub/internal/web/customer_delete.go:310 's.logger.Printf("[INFO] customer DELETE cascade COMPLETE for %s (journal #%d) — full teardown", customerID, journalID) | 6 |
| R-607 | P3 | UNCHECKED | The row asks first for a live reproduction loop (push a tag, sync, read catalog_images on a timer) and has no diagnosis. Why the sync reports 'nincs változás' while the cache moved, and when CatalogImages refreshes, are live-box behaviour I could not settle from source in the time box. No commit after d19f07ea (filing) names R-607 as fixed. | 6 |
| R-612 | P3 | STILL-TRUE-NOT-SMALL | The memory half is fixed (catalog a5a729a, 'wishlist 512M (R-612)'). The open half, making a failed first-boot seed visible, needs a new detection mechanism (read the seed's exit/log, or a probe that checks the Role/Group rows). No commit addresses it. | 4 |
| R-613 | P3 | STILL-TRUE-NOT-SMALL | uptime-kuma is fixed (catalog a5a729a). The open half is a sweep of all 58 templates for probes that pass on a setup wizard. That needs per-app live inspection, so it is not small. No commit names it. | 3 |
| R-615 | P3 | STILL-TRUE-SMALL | FIX: Before the fetch, run 'git remote set-url origin <buildRepoURL()>' (or read origin and re-clone on mismatch), and log at INFO, masked, when the remote changed. The appended drill-folder residue (stack folders the live catalog lacks) is a separate drill-teardown item and is not part of this fix. — Still inert: controller/internal/sync/sync.go:279 clones only 'if _, err := os.Stat(gitDir); os.I | 7 |
| R-616 | P3 | STILL-TRUE-SMALL | FIX: Clone and fetch with the credential-free RepoURL, and supply credentials per command via '-c http.extraHeader=Authorization: Basic <b64>' (or GIT_ASKPASS env) only when username+token are set. Pairs naturally with the R-615 set-url fix (set-url to the bare URL). — Still true: controller/internal/sync/sync.go:327-331 buildRepoURL injects 'https://%s:%s@' and the clone at :283-288 passes that U | 5 |
| R-622 | P3 | FIXED-BY-LATER-WORK | adventurelog v0.13.0 was diagnosed, fixed and promoted with a two-venue test record in app-catalog-felhom.eu 06ea7da (2026-09-27, 'adventurelog: v0.12.1 -> v0.13.0 with its health and world-data fixes in the same commit (R-655, 09 decision 41)'; bench healthy in 217 s, box 9202 through the guarded Update in 204 s). templates/adventurelog/docker-compose.yml:13 ' image: ghcr.io/seanmorley15/adven | 6 |
| R-635 | P3 | FIXED-BY-LATER-WORK | The open remainder (app_oom fires once per container run, no escalation) was built in felhom-controller 0054d4b (v0.265.0, 'OOM storm alarm', R-636). controller/internal/notify/notifier.go:746 '\t\tn.emit("app_oom_storm", "error",' fires once per run when 20 or more kills land in 30 min (:754-764, oomStormKills=20, oomStormWindowMin=30; pinned by TestR636_*). The 79 % headroom and the method lesso | 6 |
| R-645 | P3 | STILL-TRUE-NOT-SMALL | Still true for the operator CLI: controller/internal/settings/settings.go:1923-1929 ClearRestoreHold deletes ANY hold reason (including update-failed) with no pin restore, and the flag is in controller/cmd/controller/main.go:94/198. The row lists three candidate shapes with 'none chosen'. Picking one changes operator-path semantics and the capture logic, so it is not a one-hour fix. (The automatic | 6 |
| R-675 | P3 | STILL-TRUE-SMALL | FIX: In missingFileLegsRefusal, when the second drive holds a whole copy of the app (the decision-26 whole-copy check the backup manager already exposes for the restore page), name that whole restore action instead of 'Fájlok visszaállítása'. Keep the existing branch otherwise. — Unchanged: controller/internal/web/handlers.go:1745 'return head + "A fájlok a második meghajtó másolatából állíthatók | 5 |
| R-676 | P3 | STILL-TRUE-NOT-SMALL | A watch row, still true: controller/internal/stacks/unhealthy.go:116 skips only 'if st.Deploying // st.Updating // st.HoldReason != "" // st.updateHeld {', so a deploy's first start (after Deploying clears) is sampled by decision 28's crash-loop stop. The immich cause is fixed in the catalog (56c4888, 768M, per the row). Covering a slow first start would need a first-start grace decision. | 5 |
| R-682 | P3 | STILL-TRUE-NOT-SMALL | felhom-controller 7690c27 (v0.296.0): no remove journal in source — grep -rni 'remove.*journal/removeJournal/remove_intent/interrupted remove' controller/*.go returns nothing; git log --grep R-682 empty. Needs a new boot-time journal mechanism. | 3 |
| R-683 | P3 | UNCHECKED | Watch item about a power-cut drill outcome; behaviour only a live box shows; git log --grep R-683 empty in controller. | 1 |
| R-698 | P3 | NOT-WORTH-IT | PICK close-as-accepted (option a), operator to confirm: A backup records the image name/digest, not the image; restoring a version the maker deleted from the registry fails at the pull. | 2 |
| R-700 | P3 | FIXED-BY-LATER-WORK | felhom-controller 820e8ef (v0.276.0, R-697/R-700). controller/internal/stacks/migrate.go:789: 'm.logger.Printf("[INFO] [stacks] %s: data moved %s -> %s — app.yaml keeps its pin (%d service(s)) and records"' — persistDriveFlip (migrate.go:763) loads app.yaml and changes only HDD_PATH. Only a live proof on a two-drive box remains (row's own residue). | 3 |
| R-704 | P3 | FIXED-BY-LATER-WORK | felhom-controller 7cba0bf (v0.278.0). controller/internal/api/router.go:918 'func (r *Router) dropLeftoverHold(name, why string) {' calling r.sett.ClearUpdateHold(name); pinned by TestR704_AFreshInstallDropsALeftoverHold. Residue: live proof of the install-time drop only. | 2 |
| R-706 | P3 | FIXED-BY-LATER-WORK | felhom-controller 0c702f8 (v0.279.0). controller/internal/api/router.go:946 'if err := r.backupMgr.DeleteOffsiteRestoreCopy(name); err != nil {' inside removeVerificationCopy (R-706); pinned by TestR706_RemovalWithBackupsDeletesTheVerificationCopy. Residue: not seen live. | 2 |
| R-717 | P3 | STILL-TRUE-NOT-SMALL | app-catalog templates/opengist/.felhom.yml:42 and templates/wishlist/.felhom.yml:42 carry only signup_block; no after_setup/after_install in either .felhom.yml (grep empty). Fix needs per-app DB writes (opengist sqlite with app stopped) plus live proof. | 3 |
| R-723 | P3 | FIXED-BY-LATER-WORK | felhom.eu 80aeac71 (hub v0.126.0). hub/internal/monitor/staleness.go:174 '// R-723 (v0.126.0): a customer's NEW box is not a recovery.'; hub/internal/notify/dispatcher.go:540 '"suppressed", "first hour of a new box (R-723)", "operator"'. Residue: live proof at a real first install. | 2 |
| R-724 | P3 | STILL-TRUE-NOT-SMALL | Text parts fixed in controller 6be6c53 (v0.283.0). Remaining LAN/gateway read still goes only through the samba container: controller/internal/stacks/guestnet.go:47 'out, err := dockerexec.Command("docker", append([]string{"exec", sambaContainer}, args...)...).Output()' — the comment (guestnet.go:18) accepts that reads fail while sharing is off. Needs another read path (design). | 3 |
| R-728 | P3 | STILL-TRUE-SMALL | FIX: Add a per-customerID in-flight guard (sync.Map/mutex set) around the create handler from the duplicate check to the self-bind mint, so a second concurrent submit for the same ID gets the 'already exists' form; optionally disable the submit button on submit in the template. — No commit fixes R-728 (git log --grep in felhom.eu only the filing commit 11591f3a). hub/internal/web/configs.go:705 'e | 5 |
| R-729 | P3 | STILL-TRUE-NOT-SMALL | No route clears the off-site target: controller/internal/web/server.go:744-783 lists /backup/offbox/{config,toggle,enable-all,offer-dismiss,run,reset,status,restore,place,reconstitute,verify-copy/delete,confirm-escrow,inject-password}; /reset (offbox_handlers.go:311) only resets an orphaned repo. New press needs handler + settings clear + template + HU/EN copy + escrow/hub-managed-target interplay | 4 |
| R-733 | P3 | STILL-TRUE-NOT-SMALL | Harness/golden-evidence change plus a decision whether proofs run with swap off; no commit references R-733. Not a source-verifiable single fix. | 1 |
| R-738 | P3 | NOT-WORTH-IT | PICK close-as-accepted (wger defect fixed; the generic gap is a design note): The guarded Update's health check sees only an app's front page, so an app that serves its front page while its data is broken passes as done. | 3 |
| R-747 | P3 | STILL-TRUE-NOT-SMALL | Lockout shortened: app-catalog a4597cd; templates/mealie/docker-compose.yml:27 ' - SECURITY_USER_LOCKOUT_TIME=1'. Residue still open: hourly lock renewal by a stranger (needs decision 57 option d) and page copy; needs decision + live proof. | 2 |
| R-755 | P3 | DUPLICATE | of R-762 — Still true: templates/wger/docker-compose.yml has no WGER_USE_GUNICORN (grep empty). R-762 (open, read) states 'Owner decides together with R-755 (same server question)' and its fix names 'the gunicorn switch of R-755'. | 2 |
| R-756 | P3 | UNCHECKED | Depends on whether 9202's scratch drive is a registered drive — live box state; the row itself says not measured which. Not verifiable from source. | 1 |
| R-757 | P3 | STILL-TRUE-NOT-SMALL | No commit references R-757. controller/internal/stacks/deploy.go:1339-1349: 'case "secret":' ... 'value, err := generateValue(field.Generate)' ... 'appCfg.Env[field.EnvVar] = value' for any missing field of a deployed app, with no exception for fields consumed only by after_install. Fix needs a design (a marker for given-at-install fields or an ask path). | 3 |
| R-758 | P3 | STILL-TRUE-NOT-SMALL | Still true: templates/bookstack/.felhom.yml:18 ' mem_limit: "512M"' vs compose limits docker-compose.yml:42 '512M' + :79 '256M'; onboarding/EXISTING-APPS-GAPS.md:26 still lists all 8. No gate (only scripts/onboarding_gaps.py:171 reports it). Not small: raising 8 figures changes the capacity check (decision 22) — which apps fit a box — plus a gate with decoy and a publish. | 4 |
| R-762 | P3 | STILL-TRUE-NOT-SMALL | templates/wger/docker-compose.yml sets no DJANGO_DEBUG and no static/media server (grep empty); templates/wger/.felhom.yml:18 'lifecycle: hidden' (catalog 55b8c8a). Needs a server design decision (nginx sidecar vs gunicorn+static) and bench+box proof. | 2 |
| R-763 | P3 | STILL-TRUE-NOT-SMALL | templates/wger/docker-compose.yml sets neither ALLOW_REGISTRATION nor ALLOW_GUEST_USERS (grep empty); wger hidden (.felhom.yml:18 'lifecycle: hidden'). The env change is tiny but its proof needs a working wger on 9202 (blocked by R-762); best done in the same session as R-762. | 2 |
| R-774 | P3 | STILL-TRUE-NOT-SMALL | templates/karakeep has no Sentry/phone-app sentence (grep -i sentry empty); mail-ON proof needs a hub-enabled live box (demo-hp) — live work. | 2 |
| R-775 | P3 | STILL-TRUE-NOT-SMALL | Narrowed (Grimmory published behind the family gate). Residue: per-name 15-min lock is hard-coded upstream (no setting) and the reinstall-over-kept-books finding is uninvestigated — needs live investigation. | 2 |
| R-776 | P3 | STILL-TRUE-NOT-SMALL | Only bookstack has it: templates/bookstack/docker-compose.yml:32 ' - APP_PROXIES=172.16.0.0/12'; grep for TRUSTED_PROXIES/CORE_TRUST_PROXY/IPEXTRACTION/N8N_PROXY_HOPS in kimai, zipline, vikunja, nextcloud, n8n compose returns nothing. Five apps, each needing a live 3.6 re-measure on 9202. | 3 |
| R-778 | P3 | NOT-WORTH-IT | PICK close-as-accepted: If a box rolls back to a controller older than 0.286, the dashboard's login counter trusts the leftmost forwarded address and can be dodged until the box moves forward. | 2 |
| R-782 | P3 | STILL-TRUE-NOT-SMALL | Source agrees: templates/glance/docker-compose.yml:27 seeds glance.yml with no auth: block (grep 'auth' in templates/glance empty); templates/homepage has no HOMEPAGE_ALLOWED_HOSTS (grep empty). Needs live measurement on 9202 and a decision whether a public glance dashboard is intended. | 3 |
| R-783 | P3 | NOT-WORTH-IT | PICK close-as-accepted (re-open if upstream exposes the setting): Three wrong SparkyFitness sign-ins by anyone block every visitor's sign-in for about 10 seconds. | 2 |
| R-785 | P3 | STILL-TRUE-NOT-SMALL | templates/sparkyfitness/docker-compose.yml:45 ' image: codewithcj/sparkyfitness_server:v0.17.3' and :89 'codewithcj/sparkyfitness:v0.17.3'. Major-version ladder walk (bench + box), gated on R-784. | 1 |
| R-831 | P3 | NOT-WORTH-IT | PICK keep (rotation is the operator's call; do not close a leaked-secret row silently): The Hetzner storage API token was printed into one session transcript. | 1 |
| R-836 | P3 | STILL-TRUE-NOT-SMALL | Live host boot-loader work needing operator-approved reboots and measurement (GRUB env block on ESP, sp5100_tco arming). | 1 |
| R-839 | P3 | STILL-TRUE-NOT-SMALL | Gate behaves as described: controller/cmd/controller/main.go:2397 'return false, "drive " + hdd + " is not a live mountpoint"' with hdd = cfg.Env["HDD_PATH"] (main.go:2379). Which writer put a per-app path in HDD_PATH is undiagnosed — a diagnosis task, not a one-hour fix. | 4 |
| R-853 | P3 | NOT-WORTH-IT | PICK close-as-accepted: After a boot the box's versions and crash facts reach the hub up to about 15 minutes late. | 4 |
| R-862 | P3 | UNCHECKED | Waiting on the operator's by-hand bootstrap on Tester 2 through his tunnel; whether done is live-box state. No commit records it (felhom.eu log since 2026-10-04). | 1 |
| R-870 | P3 | NOT-WORTH-IT | PICK keep (operator's call; close when Tester 1 is retired): Tester 1's two Cloudflare tokens (disposable test customer) were printed into one session transcript. | 1 |
| R-879 | P3 | STILL-TRUE-NOT-SMALL | hub/internal/store/store.go:166 'retrieval_password TEXT NOT NULL,' and store.go:1586/1592 write retrieval_password and api_key as given; no seal on them (grep seal near these fields empty). Sealing/hashing three tables with migration is a security change, more than an hour. | 3 |
| R-882 | P3 | UNCHECKED | Longhorn instance-manager state on DooPlex (Tier 2, forbidden to touch); live-only, owner operator. | 1 |
| R-883 | P3 | UNCHECKED | homelab-manifests repo is not in this workspace (ls /mnt/5_hdd/felhom.eu/git shows only app-catalog-felhom.eu, drills, felhom-agent, felhom-controller, felhom.eu); live DooPlex check (kubectl) is out of scope. | 1 |
| R-886 | P3 | UNCHECKED | DooPlex Alertmanager volume ownership; homelab-manifests not in this workspace and live check not permitted. | 1 |
+29
View File
@@ -33,6 +33,35 @@ The full text of every row below: `git show ab2b3049:documentation/backlog/OPEN-
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-885** | **The Python tests under `felhom.eu/scripts/` did not run in CI (P4).** New gate `script-tests` (`scripts/script_tests_gate.py`) walks `scripts/` and runs every `test_*.py` on every push (13 suites, ~20 s); a suite's verdict is its exit code; finding none fails; nested runs step aside. The hub-DB script tests use a Python-sqlite3 stand-in when the CI runner has no `sqlite3` CLI (said out loud). Decoys (5) declared in `test_gate_decoys.py`; red-proofs R885-a/b convict. | CLOSED 2026-10-05 — FIXED (gate) | `scripts/script_tests_gate.py`; `scripts/test_script_tests_gate.py`; `audits/burndown-2026-10-05/r885-red-proof.txt` |
| **R-184** | **Nothing prevents the hub from vouching an agent version that was never released.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Fixed by felhom.eu b55fc17d "hub v0.102.0 — refuse to vouch a version that cannot be installed (R-273)" — exactly shape (b), validate at vouch time in the hub. felhom.eu/hub/internal/web/configs.go:1358 `res := s.gitea.PackageDownloadable(ctx, t.pkg, t.version, t.file)` and :1365 `s.logger.Printf("[WARN] artifact vouch REFUSED: %s package %s is NOT downloadable (R-287)", ...)`; tag leg at :1343 TagServesFile; unreachable registry also refuses. |
| **R-207** | **`DRY_RUN=1` on `node-housekeeping.sh` is NOT non-mutating — it destroys the metric history it is supposed to let you inspect** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Fixed by homelab-manifests fc9fbb8 "node_housekeeping: guard DRY_RUN, correct the expired Docker rationale, pin container log rotation". /home/kisfenyo/git/homelab-manifests/homelab-ansible/roles/node_housekeeping/templates/node-housekeeping.sh.j2:137 `if [[ "${DRY_RUN}" == "1" ]]; then` inside write_metrics, :138 logs "file left untouched". |
| **R-287** | **`felhom-agent` CI is red for a TRUE reason, and the diagnosis it was filed under is wrong in every particular.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Deleter established 2026-08-10 (R-267 newest-10 prune, recorded in the row itself); CI fixed by felhom-agent 53d047a "Two guards, one number: bound the published check to the retention it must live with" (R-291). felhom-agent@e06ed97 scripts/check-published-versions.py:101 `RETENTION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "retention-policy.json")`, :213 `keep = retention_kept()`; scripts/retention-policy.json:37 `"generic_versions_kept": 10,`. Follow-up row R-291 is open (OPEN-ITEMS.md:439). |
| **R-289** | **R-182's register row describes a defect the code no longer has — an OPEN row that is a false alarm.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | R-182 was closed by felhom.eu ef6ac6fe (2026-08-22, register compression): documentation/backlog/CLOSED-ITEMS.md:474 `/ **R-182** / ... / **CLOSED — SHIPPED** (controller v0.194.0 + hub v0.90.0/.1, 2026-08-03) /`. The residue (digest never seen delivering) was since observed: documentation/audits/DRILL-chaos-night-2026-09-17.md:181 `backup_run_failures` „1 of 12 apps failed to back up in this nightly run: nextcloud" listed as an alarm that fired and was true. |
| **R-373** | **`SysDataGrowGB` is the intended lever for the system-data volume, it works, and nothing sets it.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Premise (20G/50G two-volume mismatch, 'nothing sets SysDataGrowGB') was retired by agent v0.120.0 one-data-volume work, commit cd6e267 'v0.120.0 — one data volume (R-165...)'. felhom-agent/internal/reconcile/bringup.go:191 '// SysDataGrowGB is a COMPATIBILITY INPUT since agent v0.120.0 (R-165). There is no longer a second' and :437 'growGB := spec.DataVolGrowGB + spec.SysDataGrowGB'; installer passes it (felhom-host-install.sh:3140 '-sysdata-grow "$SYSDATA_GROW"') and records the sizing at :2041-2058. Note: cd6e267 predates the row's filing date; the row quoted an older audit. |
| **R-390** | **The golden-bake runbook omits `pveam update`, and the failure it produces names the wrong cause.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Commit 2344589a ('... runbook pveam note'); felhom.eu/documentation/runbooks/RUNBOOK-manual-build.md:154 '2. Run **`pveam update` first** — the `virgin` snapshot's template INDEX is stale too, and a stale index fails as a bogus'. |
| **R-427** | **`closed_register_gate.py` checks ONE direction only: an open word in a CLOSED row. The mirror — a CLOSED verdict on a row still sitting in `OPEN-ITEMS.md` — is unchecked, and there are TWELVE.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Commit 71b8c8c6 (Backlog triage Part B: '... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS'); felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word (CLOSED, SHIPPED,'. Of the 12 named rows, R-385/387/341/378/405/88a/88b/123 are now only in CLOSED-ITEMS.md; R-190 and R-352 remain open (partly-closed, as the row predicted). |
| **R-437** | **The register compression sweep is OWED, and it was deliberately NOT run inside the 2026-09-01 beta-line session — this row is the record of that choice, not a note.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu 71b8c8c6 'Backlog triage Part B: 125 finished rows + 20 id-less rows moved to CLOSED-ITEMS ... closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS (decoys, seen red) ... register 444 -> 325'. felhom.eu/scripts/closed_register_gate.py:10 'RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word'. Gate run today: 'closed-register gate OK — no open work filed as closed, no id in both registers.' (456 closed / 336 open, 0 convicted). |
| **R-464** | **[P3-LOW] MariaDB's entrypoint prints `MariaDB upgrade not required` on an UNSUPPORTED DOWNGRADE, so that line cannot be used as a soundness signal.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Lesson homed and harness uses the correct probe. app-catalog-felhom.eu b7ef0c4 'upgrade-test.py: record the engine's own view of its datadir'; app-catalog-felhom.eu/scripts/upgrade-test.py:278 '"mariadb-upgrade --check-if-upgrade-is-needed --user=root "'. felhom.eu d6837d98 (SPIKE R-459); felhom.eu/documentation/architecture/09-update-architecture.md:1647 '1. **Ask the engine, not the log.** MariaDB's entrypoint prints `MariaDB upgrade not required` on an' (cites R-464). |
| **R-501** | **[P3-LOW] The documented "confirm your CI run" recipe reads only the LAST page of the jobs list, and that list is not in id order — so it can report a run as missing that exists and passed.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu a4993272 'CLAUDE.md: the CI-check recipe was wrong in two ways, both measured today'. felhom.eu/CLAUDE.md:176 'rows — a run can sit several pages earlier. **Scan every page** and match on `head_sha`; with a'; recipe at CLAUDE.md:166-168 loops every page. |
| **R-602** | **[P3-LOW] The language a signed-in page uses is NOT the language a cookie asks for, and a live probe that forgets this reports a fixed defect as unfixed.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu e02bc038 'hub v0.119.0 — ... R-596/R-598 closed' added the finding; felhom.eu/documentation/architecture/10-localisation.md:809-812 'the `felhom_lang` cookie and got the **Hungarian** page for `en`. ... The cookie is the right instrument for the anonymous claim page and the **wrong**' and :509 '`langFor`'s order is fixed: `?lang=` → **the household's setting when a session exists**'. Only the optional pointer from the workspace live-validation rules is absent (grep ?lang=/felhom_lang in CLAUDE.md files and .claude/rules: none) — a 5-minute add if wanted. |
| **R-617** | **[P3-LOW] The Gitea API token this project uses for pushes cannot create a repository through the documented endpoint, but CAN through `repos/migrate` — so "the token cannot do it" was nearly recorded as a fact when the truth was "one endpoint refuses it".** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu@462ab4a5 (2026-09-22) documentation/architecture/09-update-architecture.md:1969 "`POST /api/v1/repos/migrate` is the route that works (the project's Gitea tokens carry" - continues at :1970 "`write:repository` but not `write:user`, so `POST /user/repos` answers 403"; recipe at :1979. The one-line note the row asked for exists (in the architecture doc rather than operations/). The optional operator-scoped token is a separate wish, not the defect. |
| **R-705** | **[P3-LOW] There is no way to run the night's chain now — only its pieces.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | The remaining half (manual whole-guest backup) EXISTS and predates the row: felhom-controller bbed5af (v0.47.0, 2026-06-12) 'backups page — whole-guest backup visibility + manual trigger'. Live source @7690c27: controller/internal/web/backup_handlers.go:324 `case r.URL.Path == "/api/guest-backup/trigger" && r.Method == http.MethodPost:`; :340 `if err := s.backupTrigger.TriggerNow(); err != nil {`; quiesce.go:427 "manual backup requested — quiescing now" (bypasses due-ness, all tiers); wired cmd/controller/main.go:2099 and the page button backups.html:229. Agent side: felhom-agent internal/localapi/server.go:514 `mux.HandleFunc("POST /backup", ...)`. The controller half was built v0.279.0 (night-chain, handler_debug.go:79). The row's 'no manual trigger' claim was not true at writing; it is not chained into night-chain, which the row did not require. |
| **R-766** | **[P3-LOW] A new app's logo and screenshots reach the boxes only with the next HUB release — the website alone is not enough.** (P4) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Hub releases after the assets push (felhom.eu 40f07429, 2026-10-01): hub v0.131.0 (2026-10-04) .. v0.136.0 (d4be9f6f, 2026-10-05). The build copies website assets: felhom.eu scripts/build-hub.sh:98 `cp "${WEBSITE_ASSETS_DIR}"/*-logo.svg "${BUILD_DIR}/assets/" 2>/dev/null // true`, and the hub build workspace /mnt/5_hdd/felhom.eu/build/felhom-hub/workspace/assets/ holds radicale-logo.svg + 3 screenshots (also karakeep, dawarich) dated Oct 5 14:28; hub/Dockerfile:27 `COPY assets/ /usr/share/felhom/assets-seed/`. Not checked: what a live box shows (no machine access). Checked 2026-10-05: the live hub image (v0.136.0) `/usr/share/felhom/assets-seed/` holds radicale-, karakeep- and dawarich-logo.svg + screenshots. |
| **R-50b** | **[P2] A root-owned privileged host artifact is delivered unversioned from `main` — "which wrapper is on this host?" is unanswerable.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | Claim 'fetched via fetch_raw from raw/branch/main — no tag, no pin' no longer true: bee68484 (installer v1.23.0, R-110/R-183) pinned fetch_raw to the vouched agent tag — felhom.eu scripts/felhom-host-install.sh:533 '"$GITEA_BASE/$GITEA_OWNER/$AGENT_REPO/raw/tag/v$ART_AGENT_VER/$path" \' (leg b). Leg (c)-like signed delivery: felhom-agent c9fa2e7 (R-840 config bundle) and configs/test_felhom_config_bundle.py:264 covers /usr/local/sbin/felhom-pbs-apply. Residual worth one line if kept: the 0440 sudoers drift visibility note. |
| **R-121** | **A BOX's installed agent can sit releases behind the vouched one and nothing notices — the R-120 gate does not cover it.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | 3d7a2761 (hub v0.135.0, R-530/R-604 'boxes left behind listed and alarmed'): felhom.eu hub/internal/osupdates/service.go:79 'EventAgentBehind = "agent_behind" // warning, operator' with :180 'AgentBehindAfter: a box runs an agent older than the vouched one this long → an operator alarm' (7 d window, the staleness window the row asked for). |
| **R-200** | **The DR password-injection seam has a handler, a route and tests — and no form.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | The remaining half (customer-facing recovery-code form: yell → R form → preview) shipped as the recovery screen: felhom-controller 636c51e 'R-193: the recovery screen — unlocking, and only unlocking (v0.200.0)'; controller/internal/web/templates/recovery.html:82 '<form id="unlock-form" method="POST" action="/recovery/unlock" autocomplete="off">', routed at internal/web/server.go:602. Plumbing half was 1b1366b (v0.196.0). The 64-hex inject-password route (server.go:783) stays a deliberate DR fallback with no form. |
| **R-450** | **[P2-MEDIUM] UPDATE ARC SLICE 6 — a version sequence: automatic WITHIN a major, never ACROSS one, and an engine change gets its OWN edge.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | The row's only remainder was 'the other ten PostgreSQL apps need two-venue proof'. R-463 CLOSED 2026-09-30 by felhom.eu 25cb3eb9 ('The last six PostgreSQL apps decided'): 8 of 11 moved by the box's own conversion, 3 (zipline, adventurelog, immich) stay by decision 42; CLOSED-ITEMS.md:223. Source proof: app-catalog templates/docmost/docker-compose.yml:62 'image: postgres:18-alpine' (also rallly:67, outline:64, paperless-ngx:101). The per-app engine gate stays as the permanent rule (catalog CLAUDE.md:115-122). |
| **R-489** | **[P3-LOW] `POST /api/stacks/{name}/remove` reports `volumes_removed: null` over named volumes it DID remove.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | The residual (a unit-restore-recreated volume has no compose label, so the remove answered []) was fixed in felhom-controller 206b035 (v0.268.0, R-658). controller/internal/stacks/delete.go:1089-1090: '// appVolumeSet is every volume the removal accounts for: the ones carrying the project label AND the // ones the app's definition declares that Docker holds by name (R-658, v0.268.0).' delete.go:703 'resp.VolumesRemoved = removedVolumes(volsBefore, m.appVolumeSet(name, stackDir))'. |
| **R-573** | **[P3-LOW] The agent-channel and endpoint-drift banners reach the dashboard as finished Hungarian, so they stay Hungarian on an English page.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom-controller 7c4a33b (v0.258.0, 'the last four Hungarian things an English household met ... R-573 the two channel banners'). controller/internal/web/alerts.go:113 'func (am *AlertManager) SetAgentChannelAlert(down bool, msgKey, msg string) {' and :136 'func (am *AlertManager) SetEndpointDriftAlert(drift bool, msgKey, msg string) {'. Both set MessageKey with msg only as a fail-open fallback. |
| **R-622** | **[P2-MEDIUM] `adventurelog v0.13.0` migrates the customer's database and then does not serve — the edge must NOT be promoted, and it is the first real-catalog candidate this project has measured as unsafe.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | adventurelog v0.13.0 was diagnosed, fixed and promoted with a two-venue test record in app-catalog-felhom.eu 06ea7da (2026-09-27, 'adventurelog: v0.12.1 -> v0.13.0 with its health and world-data fixes in the same commit (R-655, 09 decision 41)'; bench healthy in 217 s, box 9202 through the guarded Update in 204 s). templates/adventurelog/docker-compose.yml:13 ' image: ghcr.io/seanmorley15/adventurelog-backend:v0.13.0'. The proven step is recorded at templates/adventurelog/.felhom.yml:132 (update_ladder entry from v0.12.1). |
| **R-635** | **[P1-HIGH] `romm 5.3.0` does not fit the memory the template gives it, and the guarded Update called that a success — the app has been OOM-crash-looping on demo-hp for six hours at ~500% CPU.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | The open remainder (app_oom fires once per container run, no escalation) was built in felhom-controller 0054d4b (v0.265.0, 'OOM storm alarm', R-636). controller/internal/notify/notifier.go:746 '\t\tn.emit("app_oom_storm", "error",' fires once per run when 20 or more kills land in 30 min (:754-764, oomStormKills=20, oomStormWindowMin=30; pinned by TestR636_*). The 79 % headroom and the method lesson are carried by R-462 (per the row). |
| **R-235** | **The appliance console keeps telling an already-paired box to go and pair itself.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu c033b3b6 'ISO 1.28.0 source: the console stops showing the pairing code once bound (R-535)'. scripts/iso/felhom-bootstrap.sh:538: `print_bound_banner # R-535: replace the pairing code on the console with the truth`. Same defect already CLOSED twice in CLOSED-ITEMS.md as R-535 (line 232) and R-214 (line 200, 'proven on a fresh install'). |
| **R-282** | **One secret, three different Hungarian names, and the email sends the customer to a page their box is not showing.** (P3) | CLOSED 2026-10-05 — FIXED BY LATER WORK (burn-down Part A) | felhom.eu 4d6ec7c 'hub v0.104.0: ... the hub half of the naming (R-295)' + controller v0.211.0 (R-295 CLOSED, CLOSED-ITEMS.md:207) + R-323 hub v0.105.0. hub/internal/notify/templates.go:204: '// R-295, HUB HALF (2026-08-13). ONE NAME PER SECRET, and it is „Beállító kód".'; hub/internal/claim/engine.go:51 `EmailReenroll EmailKind = "reenroll"` (mail names the setup page a rebuilt box shows). |
| **R-376** | **The placement decision that cost four mis-filed defect reports was never recorded as a decision anywhere, and the architecture folder's own marker convention lives in one document of eight.** (P4) | CLOSED 2026-10-05 — DONE | The legend reached the three documents written after the 2026-08-22 pass (`08`, `09`, `11`); all eleven numbered architecture documents now carry it (`grep -c "not yet classified"` = 1 each; `07` is the original home). The hot/bulk decision was given its home on 2026-08-22 (`01` [DESIGN] + CONTEXT). Marking every statement stays a practice of each session that touches a document, not a defect. |
| **R-817** | **`09` decision 56 and R-745 disagree about what the controller self-update rolls back to.** (P4) | CLOSED 2026-10-05 — CLARIFIED (no contradiction) | `felhom-agent internal/localapi/controllerswap.go:236-240` records the image running when a swap starts as `Previous`; `:289` writes it back on failure. After a good swap that is „the one before" the running image — decision 56 and R-745 name the same image. A dated clarification sits under decision 56 in `09`; the ruling text is unchanged. |
| **R-818** | **Two changelogs cite register ids for other findings.** (P4) | CLOSED 2026-10-05 — CORRECTED | Dated correction notes under hub v0.109.0 (`hub/CHANGELOG.md`) and controller v0.224.0 + v0.225.0 (`felhom-controller/CHANGELOG.md`): those two findings never had register rows of their own — the triage's „the real ids are in CLOSED-ITEMS" was itself wrong (no closed row names hub v0.109.0 or controller v0.224.0/v0.225.0). Nothing renumbered. |
| **R-755** | **[P3-LOW] wger runs Django's DEVELOPMENT server in production: `manage.py runserver`, because the template does not set `WGER_USE_GUNICORN=True`.** (P3) | CLOSED 2026-10-05 — DUPLICATE of R-762 (its unique fact moved there) | Still true: templates/wger/docker-compose.yml has no WGER_USE_GUNICORN (grep empty). R-762 (open, read) states 'Owner decides together with R-755 (same server question)' and its fix names 'the gunicorn switch of R-755'. |
| **R-446** | **[P2-MEDIUM] „Naprakész" can be FALSE, and the badge that says it cannot tell.** (P3) | CLOSED 2026-10-05 — DUPLICATE of R-440 (its unique fact moved there) | felhom-controller/controller/internal/stacks/updateorder.go:96: `if len(s.CatalogDigests) == 0 // s.CatalogTestedAt.IsZero() { return false }` — blind only for apps with no ladder entry, i.e. the same 15 templates R-440 lists (app-catalog has no update_ladder for them). Both rows close by the same act: each app's first proven ladder step (R-462). |
---
File diff suppressed because one or more lines are too long
+2
View File
@@ -758,6 +758,8 @@ expensive mistake. That asymmetry is the same one `looksLikeRepositoryDamage` is
## v0.109.0 — the Backup card told every operator that every customer had no backups (2026-08-30, R-331)
> **Correction 2026-10-05 (R-818):** the ids **R-330** and **R-331** in this entry are mislabels. These two findings never had register rows of their own; the register's R-330 and R-331 are disk-health Phase 2 and Phase 3. Read „R-330" here as *the nightly false app alarm* (controller v0.224.0) and „R-331" as *the off-site snapshot count read as a measured zero* (hub v0.109.0 Backup card, controller v0.225.0 `stats_known`). Nothing was renumbered.
> **This push used `git push --no-verify`, and that is declared here rather than worked around.**
> `golden_currency_gate.py` was CONVICTED: controller v0.224.0 and v0.225.0 are released and the newest
> golden bake carries 0.223.0, so a machine installed right now receives neither. **A bypass, not a