NOTHING IN THE CATALOG CHANGED. No template, no .felhom.yml, no
templates.json, no image pin. Output is audits/pikapods-scan-2026-10-10/.
THE GREEN HEART, in PikaPods' own words. Hovering it shows the tooltip
"Project has a revenue sharing agreement." (verified by hovering the heart
beside Actual, A1-heart-tooltip.jpg). Their FAQ: "Open source developers have
the option to enter into a revenue sharing agreement to receive 20% of
revenues their app generates." The mark is NOT permission for Felhom -- it is
an agreement with PikaPods, on terms we have not seen. It only shows the
author is open to such an arrangement at all.
Finding the mark took reading the DOM: the hearts are not text and not a
title attribute. Each card carries three Vuetify icons, all three present on
every card, and the green one (rgb(76,175,80)) is display:none where there is
no agreement. Counting "hearts in the page text" would have returned zero on
all 126.
NUMBERS. 126 apps on PikaPods, 39 marked. 58 template dirs here (56 offered +
plant-it abandoned + wger hidden -- which reconciles with the website's 56 +
FileBrowser built-in). 25 on both, 33 of ours not there, 101 of theirs not
ours, 11 of the 25 shared carry the mark.
LICENCE COMFORT: one row moved, and I will not dress it up as more.
sparkyfitness is there WITH the mark, consistent with what its author already
told us (R-784) -- so he demonstrably does commercial-hosting agreements.
tandoor, emby, plex, outline, calcom and wanderer are NOT on PikaPods at all,
so the scan says nothing about them. n8n and docmost ARE hosted there WITHOUT
a mark: a fact about PikaPods' risk appetite, not a permission we can borrow.
TEN CANDIDATES, none added, each licence read at its own repo and cited:
Grocy (MIT), Firefly III (AGPL-3.0), Monica (AGPL-3.0), LubeLogger (MIT),
Storyteller (MIT), PdfDing (AGPL-3.0), Memos (MIT), Wealthfolio (AGPL-3.0),
Kavita (GPL-3.0), PhotoPrism (AGPL-3.0). Developer and company tools were
excluded wholesale. Each would still go through NEW-APP-CHECKLIST.md.
JOPLIN WAS SHORTLISTED AND DROPPED ON ITS LICENCE, which is the most useful
thing in this scan. Its repo root says AGPL-3.0-or-later "unless a directory
contains a LICENSE or LICENSE.md file" -- and packages/server, the part we
would host, carries the Joplin Server Personal Use License: "the Software may
be used for personal non-commercial purposes only", and the licensee may not
"grant others the right to use the Software for a fee". Same shape as
SparkyFitness: open client, restricted server. GitHub's repo-level licence
field says AGPL-3.0 and would have hidden it.
Two other reads worth keeping: PdfDing's GitHub mirror has NO licence file at
all (the project lives on Codeberg, where it is AGPL-3.0) -- trusting the
mirror would have called it unlicensed; and photoprism/joplin both report
NOASSERTION to the API, so both needed the file read rather than the badge.
Register: 0 opened, 0 closed. The brief expected a row only for a licence
problem in an app we already ship; none turned up. No contact with PikaPods or
any author.
The register grep that put these at R-158..R-161 was true when run and stale within hours: the
parallel session pushed SPIKE-recovery-unit-space-2026-08-02.md and CAMPAIGN-10-closeout-2026-08-02.md
mid-run, both using R-158 for an unrelated finding, and neither files it — ROADMAP.md and
OPEN-ITEMS.md still stop at R-155.
So two sessions minted the same number for different findings on the same day, which is the exact
failure §8.0 was already documenting about R-154/R-155 and R-156/R-157. Now recorded with itself as
the third instance. R-156, R-157 and R-158 are all live in audit documents and none is filed.
Layer 1: guest 9301 destroyed, vm-9301-disk-0 removed, pct list clean.
Layer 2: local-lvm available returned to 258 702 410 KiB — EXACTLY the pre-run baseline (29.27%).
Layer 3: no hub record was ever created (the scratch guest ran no controller and was never
enrolled, by design); confirmed absent from /configs and /hosts after teardown.
papra referral updated: c10-soak was torn down by the other session mid-run and papra disappeared
from hub telemetry with grafana, homebox and rallly — Campaign 10's four discriminator apps (§A4).
So papra's one deployment was on c10-soak. The fix is STILL not applied: that is absence evidence,
demo-hp is fenced, and applying it wrongly is irreversible while leaving it is a one-line push.
The single command that settles it is recorded.
Campaign 10's R-156 found papra writing its database into the container's writable layer while the
volume the template preserves stayed empty — a backup that completes, verifies, and contains
nothing. papra was never the point: nothing anywhere checked that the folder a template preserves
is the folder the app writes to. All 53 templates have now been measured live.
43 CLEAN / 3 BROKEN / 7 UNDETERMINED. UNDETERMINED is counted separately, each with its reason,
and never folded into CLEAN.
FIXED (neither app is deployed anywhere, so nothing was stranded):
- gramps-web mounted /app/data, /app/media, /tmp — and /app/data is a path the application never
writes. Its accounts database and ITS FAMILY TREE both landed in the writable layer while
gramps_data was tarred nightly as an empty directory. Now persists the eight paths the image's
own environment names, matching upstream's reference compose. Proven: users.sqlite and the
family-tree files survive a redeploy byte-identical, same inode.
- wishlist mounted wishlist_data:/data, another path the app never writes; prod.db landed in the
ANONYMOUS volume from the image's VOLUME directive — absent from ResolveDockerVolumeNames, so
never backed up, and orphaned by a redeploy. Now mounts /usr/src/app/data + /usr/src/app/uploads.
Proven: prod.db byte-identical, same inode, across a redeploy.
Every corrected path confirmed by two independent sources — the shipped image's own
environment/Config.Volumes and upstream's reference compose — never inferred from a directory name.
papra is NOT fixed. It is live on one box, and changing the mount target makes the next compose up
recreate the container and destroy the writable layer its documents live in. The fix is prepared
and proven in the scratch guest (current: db.sqlite differs after a redeploy, so a real account
created via the API is lost; fixed: byte-identical, it survives). Referred to the operator with the
two options; no migration written.
NEW GATE scripts/check-volume-persistence.py — the third catalog gate and the only RUNTIME one.
This class is invisible to static analysis, measured not assumed: a static audit of all 53 composes
reports the catalog clean AND reports papra clean. Exit 0 clean / 1 REFUSED / 2 undecided. It
refuses to report at all unless it has just re-proven itself in both directions against two canary
templates that differ only in which path the volume mounts at, so every run carries a live
demonstration of R-156 and of its fix. No docker exec anywhere (Campaign 7 §1.1). 44 fixture tests
driving check(), the function __main__ calls; every rule red-proofed.
Enforcement is convention, not CI — this repo has no CI. Stated plainly in the report; raising it
is proposed as R-160.
Report, per-app evidence, proofs and proposed register entries (R-158..R-161, NOT filed — felhom.eu
is fenced this session): audits/persistence-sweep-2026-08-02/