Family gate session close: B4 on demo-hp (real internet: member in, stranger 401; remove with data), REPORT-family-gate-2026-10-02, STATUS, R-800 (remove-with-data keeps userdata, operator), CONTEXT; register 436
gates / gates (push) Successful in 26s
gates / gates (push) Successful in 26s
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:
@@ -0,0 +1,5 @@
|
||||
##### B4 on demo-hp 9201 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 96829d0 Grimmory and MeTube published behind the family gate (controller >= 0.287.0, decis | 2026-10-02T07:38:25Z
|
||||
before: gate files: none | drive: /dev/nvme0n1 938G 74G 817G 9% /mnt/felhom-drives/hdd_1
|
||||
the public names resolve to: 2a06:98c1:3121::3 video.enkisfelhom.hu
|
||||
2a06:98c1:3120::3 video.enkisfelhom.hu (Cloudflare — the REAL internet path)
|
||||
Család card: add b4teszt -> ok=True (password returned once, len 19, not printed)
|
||||
@@ -0,0 +1,12 @@
|
||||
deploy grimmory (fields beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'], values not printed) -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
gate files right after the press: family-gate-grimmory.yml setup-gate-grimmory.yml
|
||||
deployed in 85 s: state=running pinned={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.5.0', 'grimmory-db': 'mariadb:11.4'}
|
||||
family_gate record: family_gate: since: "2026-10-02T07:38:31Z" hosts: - library.enkisfelhom.hu
|
||||
the household makes the first admin through the setup gate -> 200
|
||||
setup gate opened by its probe after ~10 s; gate files: family-gate-grimmory.yml
|
||||
NOTE: attempt 1 stopped on a HARNESS error: after Grimmory's setup gate opened, box_walk still held the SETUP-gate cookie, which the family gate rightly refused (401 on an API path, no redirect, so no retry). The product behaved as designed. Cleaned up through the product (remove with data, member removed) before attempt 2.
|
||||
cleanup: remove grimmory WITH data -> ('200', {'ok': True, 'data': {'removed': 'grimmory', 'volumes_removed': ['grimmory_grimmory_db'], 'hdd_paths_removed': ['/mnt/felhom-drives/hdd_1/appdata/grimmory/data (4.0K)'], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives/hdd_1/backups/primary/grimmory (32K)'], 'verified': True}, 'message': 'Stack grimmory removed'})
|
||||
cleanup: remove member b4teszt -> {"data":{"members":[],"name":"b4teszt"},"error":"","ok":true}
|
||||
|
||||
left: /mnt/felhom-drives/hdd_1/appdata/grimmory
|
||||
/mnt/felhom-drives/hdd_1/userdata/media/grimmory
|
||||
@@ -0,0 +1,7 @@
|
||||
##### B4 on demo-hp 9201 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 96829d0 Grimmory and MeTube published behind the family gate (controller >= 0.287.0, decis | 2026-10-02T08:10:07Z
|
||||
before: gate files: none | drive: /dev/nvme0n1 938G 74G 817G 9% /mnt/felhom-drives/hdd_1
|
||||
the public names resolve to: 2a06:98c1:3120::3 video.enkisfelhom.hu
|
||||
2a06:98c1:3121::3 video.enkisfelhom.hu (Cloudflare — the REAL internet path)
|
||||
Család card: add b4teszt -> ok=True (password returned once, len 19, not printed)
|
||||
Család card: remove b4teszt -> True | members now: []
|
||||
after: gate files: none | stacks: 0
|
||||
@@ -0,0 +1,17 @@
|
||||
deploy grimmory (fields beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'], values not printed) -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
gate files right after the press: family-gate-grimmory.yml setup-gate-grimmory.yml
|
||||
deployed in 80 s: state=running pinned={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.5.0', 'grimmory-db': 'mariadb:11.4'}
|
||||
family_gate record: family_gate: since: "2026-10-02T08:10:13Z" hosts: - library.enkisfelhom.hu
|
||||
the household makes the first admin through the setup gate -> 200
|
||||
setup gate opened by its probe after ~10 s; gate files: family-gate-grimmory.yml
|
||||
grimmory: readback http=200 found=True (controls: no token 401, wrong 401, second setup 403)
|
||||
a book uploaded -> 204 | read back: True
|
||||
on the drive: /mnt/felhom-drives/hdd_1/userdata/media/grimmory/Felhom/Felhom Demo 7ca212a4/Felhom Demo 7ca212a4 - Felhom.epub |
|
||||
a STRANGER / internet 401 | LAN 401
|
||||
a STRANGER /api/v1/books internet 401 | LAN 401
|
||||
a STRANGER /api/v1/opdsx internet 401 | LAN 401
|
||||
a STRANGER /api/v1/opds internet 401 | LAN 401
|
||||
the family member b4teszt through the REAL internet: the app's address → the family sign-in → back: 403 at library.enkisfelhom.hu/ (the app's own page: False)
|
||||
2.6 remove WITH data and backups -> 200 {'ok': True, 'data': {'removed': 'grimmory', 'volumes_removed': ['grimmory_grimmory_db'], 'hdd_paths_removed': ['/mnt/felhom-drives/hdd_1/appdata/grimmory/data (4.0K)'], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives/hdd_1/backups/primary/grimmory (32K)'], 'verified': True},
|
||||
left after: /mnt/felhom-drives/hdd_1/userdata/media/grimmory/Felhom/Felhom Demo 7ca212a4/Felhom Demo 7ca212a4 - Felhom.epub | /mnt/felhom-drives/hdd_1/appdata/grimmory | /mnt/felhom-drives/hdd_1/userdata/media/grimmory
|
||||
gate files: family-gate-grimmory.yml family-gate-metube.yml | a stranger at library/ -> internet 404 | LAN 404
|
||||
@@ -0,0 +1,13 @@
|
||||
##### B4 member through the real internet — 2026-10-02T08:40:07Z
|
||||
control: Python's user agent at the edge -> 403 | a browser's -> 404 (MeTube not installed yet: 404 from the box)
|
||||
deploy metube -> 202
|
||||
state=running
|
||||
Család card: add b4teszt -> ok=True (password returned once, not printed)
|
||||
a member's browser opens video.enkisfelhom.hu (no cookie) -> 200 at https://felhom.enkisfelhom.hu/__family/start (the family sign-in page: True)
|
||||
the member signs in on the box's own page -> 200 at https://video.enkisfelhom.hu/ (MeTube's own page: True)
|
||||
the member reads /history -> 200 (True)
|
||||
the same browser at the DASHBOARD (/launcher) -> 200 at https://felhom.enkisfelhom.hu/login (sent to the dashboard login: True)
|
||||
logout -> 200; then /history -> 200 at https://felhom.enkisfelhom.hu/__family/start (refused: True)
|
||||
a STRANGER (no cookie, browser UA) at video.enkisfelhom.hu/history -> 401
|
||||
remove metube WITH data and backups -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját adatot külső megh
|
||||
Család card: remove b4teszt -> ok=True members now []
|
||||
@@ -0,0 +1,16 @@
|
||||
deploy metube (fields beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'], values not printed) -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
gate files right after the press: family-gate-grimmory.yml family-gate-metube.yml
|
||||
deployed in 40 s: state=running pinned={'metube': 'ghcr.io/alexta69/metube:2026.09.29'}
|
||||
family_gate record: family_gate: since: "2026-10-02T08:12:13Z" hosts: - video.enkisfelhom.hu
|
||||
metube: POST /add http=200 {"status": "ok"}
|
||||
metube: the download finished (991017 bytes)
|
||||
metube: history has it=True; GET /download/<file> http=200
|
||||
the seed reads back: True
|
||||
on the drive: /mnt/felhom-drives/hdd_1/userdata/media/metube/Big_Buck_Bunny_360_10s_1MB.mp4 |
|
||||
a STRANGER / internet 401 | LAN 401
|
||||
a STRANGER /history internet 401 | LAN 401
|
||||
a STRANGER /socket.io/?EIO=4&transport=polling internet 401 | LAN 401
|
||||
the family member b4teszt through the REAL internet: the app's address → the family sign-in → back: 403 at video.enkisfelhom.hu/ (the app's own page: False)
|
||||
2.6 remove WITH data and backups -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját adatot külső meghajtón, így ott nem volt mit törölni.', 'verified': True}, 'message': 'Stack metube removed'}
|
||||
left after: /mnt/felhom-drives/hdd_1/userdata/media/metube/Big_Buck_Bunny_360_10s_1MB.mp4 | /mnt/felhom-drives/hdd_1/userdata/media/metube
|
||||
gate files: none | a stranger at video/ -> internet 404 | LAN 404
|
||||
@@ -0,0 +1,14 @@
|
||||
## 2026-10-02T08:41:34Z demo-hp 9201 teardown (three layers)
|
||||
machine — before the hand tidy:
|
||||
/mnt/felhom-drives/hdd_1/userdata/media/grimmory/Felhom/Felhom Demo 7ca212a4/Felhom Demo 7ca212a4 - Felhom.epub
|
||||
/mnt/felhom-drives/hdd_1/userdata/media/metube/Big_Buck_Bunny_360_10s_1MB.mp4
|
||||
0
|
||||
0
|
||||
1
|
||||
the test files above are this session's own (a test EPUB, the 1 MB test clip) — removed by hand with the empty test folders; the product kept them on purpose (userdata = the household's files, R-800):
|
||||
audiobooks books comics movies music photos podcasts tv
|
||||
adventurelog adventurelog-frontend adventurelog-postgres bentopdf bookstack bookstack-db calibre-web cloudflared docmost docmost-postgres docmost-redis felhom-controller filebrowser kimai kimai-db opengist paperless-postgres paperless-redis paperless-webserver privatebin romm romm-db romm-redis traefik
|
||||
host — no guest, storage or file created on demo-hp itself (9201 only).
|
||||
hub — read only; the floor and the artifact manifest changed on purpose (E/floor.txt, golden record); no customer or appliance record created.
|
||||
Család card on demo-hp: members [] (b4teszt removed).
|
||||
note: the 1 family-gate file counted above was MeTube's, read ~10 s after its removal; the box's tick removed it at 08:41:36 ("removed the family-gate file of an app that is not installed"); video.enkisfelhom.hu answers 404 after. Read 2026-10-02T08:42Z.
|
||||
@@ -69,6 +69,7 @@ def deploy(app, sub):
|
||||
|
||||
|
||||
w.login()
|
||||
w.sync_rescan("metube", "ghcr.io/alexta69/metube:2026.09.29")
|
||||
say(None, f"##### B4 on demo-hp 9201 — controller {w.guest('docker inspect felhom-controller --format {{.Config.Image}}').strip()} | catalog "
|
||||
+ w.guest("cd /opt/docker/felhom-controller/data/catalog-cache 2>/dev/null || cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache; git log --oneline -1").strip()[:90]
|
||||
+ f" | {time.strftime('%FT%TZ', time.gmtime())}")
|
||||
@@ -95,6 +96,7 @@ for app, (sub, fx) in APPS.items():
|
||||
if "setup-gate-grimmory" not in gate_files(): break
|
||||
time.sleep(5)
|
||||
say(app, f" setup gate opened by its probe after ~{5*i} s; gate files: {gate_files()}")
|
||||
w.GATE.pop(sub, None) # the SETUP-gate cookie is useless now; the family gate wants a family cookie (attempt 1)
|
||||
code, tok = fx._token(w, sub, "admin", pw)
|
||||
auth = ["-H", f"Authorization: Bearer {tok}"]
|
||||
rc, c, out = w.app_curl(sub, "/api/v1/libraries", *auth, "-H", "Content-Type: application/json",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""fg_b4_lib.py — the demo-box helpers fg_b4_member.py uses (deploy, the Család card, remove), product endpoints only."""
|
||||
import json, os, secrets, subprocess, sys, time
|
||||
os.environ.setdefault("GUEST", "9201"); os.environ.setdefault("BASE", "https://192.168.0.155")
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w
|
||||
w.HP = "hp"
|
||||
DOM = w.DOMAIN; CTL = f"felhom.{DOM}"; DRIVE = "/mnt/felhom-drives/hdd_1"
|
||||
|
||||
|
||||
def _card(action, name):
|
||||
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip(); csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip()
|
||||
r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}", "-X", "POST",
|
||||
"--data", f"name={name}", f"{w.BASE}/family/members/{action}"], capture_output=True, text=True)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
|
||||
def add_member(name, say):
|
||||
d = _card("add", name)
|
||||
say(f"Család card: add {name} -> ok={d['ok']} (password returned once, not printed)")
|
||||
return d["data"]["password"]
|
||||
|
||||
|
||||
def remove_member(name, say):
|
||||
d = _card("remove", name)
|
||||
say(f"Család card: remove {name} -> ok={d['ok']} members now {d['data'].get('members')}")
|
||||
|
||||
|
||||
def deploy(app, sub, say):
|
||||
code, d = w.ctl("GET", f"/api/stacks/{app}/deploy-fields")
|
||||
fields = (((d.get("data") or {}).get("metadata") or {}).get("deploy_fields")) or []
|
||||
values = {"DOMAIN": DOM, "SUBDOMAIN": sub}
|
||||
for f in fields:
|
||||
ev, ty = f.get("env_var"), f.get("type")
|
||||
if ev in values or (not f.get("required") and ty != "password"):
|
||||
continue
|
||||
values[ev] = DRIVE if ty == "path" else ("Demo-" + secrets.token_hex(12) if ty in ("secret", "password") else (f.get("default") or app))
|
||||
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": values})
|
||||
say(f"deploy {app} -> {code}")
|
||||
for _ in range(120):
|
||||
time.sleep(5)
|
||||
st = w.stack(app)
|
||||
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") == "running":
|
||||
break
|
||||
say(f" state={st.get('state')}")
|
||||
|
||||
|
||||
def remove(app, say):
|
||||
w.ctl("POST", f"/api/stacks/{app}/stop")
|
||||
for _ in range(24):
|
||||
time.sleep(5)
|
||||
if w.stack(app).get("state") != "running":
|
||||
break
|
||||
code, d = w.ctl("POST", f"/api/stacks/{app}/remove", {"remove_hdd_data": True, "remove_backups": True})
|
||||
say(f"remove {app} WITH data and backups -> {code} {str(d)[:200]}")
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fg_b4_member.py — B4 part 2: a family member signs in through the REAL internet (Cloudflare → demo-hp's tunnel) with a
|
||||
browser's user agent. Attempt 2 of fg_b4.py used Python's urllib, whose user agent Cloudflare refuses at its edge (403,
|
||||
"error code 1010") before the box — measured. MeTube is installed through the product, a test member is added on the
|
||||
Család card, the member signs in with curl (cookie jar, browser UA) on the box's own page, reaches MeTube, then logs out
|
||||
(and is refused after); a stranger's cookie-less request is refused through the same path. Then MeTube is removed with
|
||||
its data and the member removed. Secrets never printed. Evidence: $EV/B4/member-internet.txt."""
|
||||
import html, json, os, re, secrets, subprocess, sys, tempfile, time
|
||||
os.environ.setdefault("GUEST", "9201"); os.environ.setdefault("BASE", "https://192.168.0.155")
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import fg_b4_lib as L
|
||||
w = L.w
|
||||
log = open(f"{w.EV}/B4/member-internet.txt", "a", buffering=1)
|
||||
def say(*a):
|
||||
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
|
||||
UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0 Safari/537.36"
|
||||
jar = tempfile.mktemp(prefix="b4jar-")
|
||||
def c(url, *a, data=None):
|
||||
args = ["curl", "-s", "-L", "--max-time", "30", "-A", UA, "-b", jar, "-c", jar, "-H", "Accept: text/html", "-o", "/tmp/b4body",
|
||||
"-w", "%{http_code} %{url_effective}"]
|
||||
if data is not None: args += ["--data", data]
|
||||
r = subprocess.run(args + list(a) + [url], capture_output=True, text=True)
|
||||
code, _, eff = r.stdout.strip().partition(" ")
|
||||
body = open("/tmp/b4body", encoding="utf-8", errors="replace").read() if os.path.exists("/tmp/b4body") else ""
|
||||
return code, eff.split("?")[0], body
|
||||
|
||||
w.login()
|
||||
say(f"##### B4 member through the real internet — {time.strftime('%FT%TZ', time.gmtime())}")
|
||||
say("control: Python's user agent at the edge ->", subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-A", "Python-urllib/3.13", "https://video.enkisfelhom.hu/"], capture_output=True, text=True).stdout,
|
||||
"| a browser's ->", subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-A", UA, "https://video.enkisfelhom.hu/"], capture_output=True, text=True).stdout, "(MeTube not installed yet: 404 from the box)")
|
||||
L.deploy("metube", "video", say)
|
||||
fpw = L.add_member("b4teszt", say)
|
||||
code, url, body = c("https://video.enkisfelhom.hu/")
|
||||
say(f"a member's browser opens video.enkisfelhom.hu (no cookie) -> {code} at {url} (the family sign-in page: {'name=\"_ft\"' in body})")
|
||||
ft = re.search(r'name="_ft" value="([^"]+)"', body); rd = re.search(r'name="rd" value="([^"]*)"', body)
|
||||
form = "&".join(f"{k}={__import__('urllib.parse').parse.quote(v)}" for k, v in
|
||||
(("_ft", ft.group(1) if ft else ""), ("rd", html.unescape(rd.group(1)) if rd else ""), ("name", "b4teszt"), ("password", fpw)))
|
||||
code, url, body = c("https://felhom.enkisfelhom.hu/__family/login", data=form)
|
||||
say(f"the member signs in on the box's own page -> {code} at {url} (MeTube's own page: {'MeTube' in body and 'name=\"_ft\"' not in body})")
|
||||
code, url, body = c("https://video.enkisfelhom.hu/history")
|
||||
say(f"the member reads /history -> {code} ({'done' in body})")
|
||||
code, url, body = c("https://felhom.enkisfelhom.hu/launcher")
|
||||
say(f"the same browser at the DASHBOARD (/launcher) -> {code} at {url} (sent to the dashboard login: {'/login' in url})")
|
||||
code, url, body = c("https://felhom.enkisfelhom.hu/__family/logout", data="")
|
||||
code2, url2, _ = c("https://video.enkisfelhom.hu/history")
|
||||
say(f"logout -> {code}; then /history -> {code2} at {url2} (refused: {'video.' not in url2 or code2 != '200'})")
|
||||
say("a STRANGER (no cookie, browser UA) at video.enkisfelhom.hu/history ->",
|
||||
subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-A", UA, "https://video.enkisfelhom.hu/history"], capture_output=True, text=True).stdout)
|
||||
L.remove("metube", say)
|
||||
L.remove_member("b4teszt", say)
|
||||
os.unlink(jar)
|
||||
@@ -911,7 +911,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-797** | **[P3-LOW] `check-family-gate.py` rule 3 (a family_gate template needs a baked golden ≥ 0.287.0) is checked only where the felhom.eu sibling exists — CI's single clone cannot.** MEASURED 2026-10-02 (`audits/family-gate-2026-10-02/C-metube-bench/C3b-family-gate-with-sibling.txt`): without the sibling the gate printed NOT CHECKED and its summary read OK. The summary line now says "rule 3 … NOT CHECKED here". The pre-push hook (with the sibling) is where it bites. **Needs:** nothing more unless CI gets the sibling. | **WATCHING — rank P3-LOW; owner: CC** |
|
||||
| **R-798** | **[P3-LOW] Grimmory's template sets `SWAGGER_ENABLED=false`, which v3.5.0 does not read (it reads `API_DOCS_ENABLED`, default false).** READ 2026-10-02 (`audits/family-gate-2026-10-02/B/grimmory-reads/upstream-v3.5.0.txt`). Harmless today — the API docs are off by default. **Needs:** remove the dead line or set the right name, on the next Grimmory step. | **READY — rank P3-LOW; owner: CC** |
|
||||
| **R-799** | **[P3-LOW] The MeTube fixture's `POST /add` leaves out `download_type`, which upstream's validator lists as required.** MEASURED 2026-10-02 (`audits/family-gate-2026-10-02/C-metube-bench/`): both 2026.09.28 and .29 answered 200 and downloaded anyway. Fragile if a later tag enforces it. **Needs:** add `download_type: video` to the fixture with the next MeTube step. | **READY — rank P3-LOW; owner: CC** |
|
||||
| **R-800** | **[P3-LOW] Removing MeTube "keeping data" answers `hdd_paths_preserved: []` although its downloads stay on the drive.** MEASURED 2026-10-02 on 9202 (`audits/family-gate-2026-10-02/B/box/life.txt`, `audits/family-gate-2026-10-02/B/box/final.txt`): the remove parses MeTube's compose as "0 HDD mounts" — its only drive mount is `${USERDATA_PATH}`, not `${HDD_PATH}` — so the result lists nothing kept; the files WERE kept and the restore used them. Other userdata-only apps (jellyfin, komga, …) have the same shape. **Needs:** check what the household's remove dialog says for such an app; list userdata mounts as kept. | **READY — rank P3-LOW; owner: CC** |
|
||||
| **R-800** | **[P2-MEDIUM] "Delete your data from the hard drive" keeps the app's files in the household's userdata folder — and neither the dialog nor the result says so.** MEASURED 2026-10-02. On demo-hp (live catalog, `audits/family-gate-2026-10-02/B4/grimmory-demo.txt`, `metube-demo.txt`): remove WITH data removed Grimmory's database volume, appdata and backups but KEPT the uploaded EPUB in `userdata/media/grimmory`; for MeTube the dialog offers no data checkbox at all and the result says "the app stored no data of its own on a drive" while the downloaded video stays in `userdata/media/metube`. On 9202 "keeping data" answers `hdd_paths_preserved: []` for the same reason (`audits/family-gate-2026-10-02/B/box/life.txt`): the remove reads `${HDD_PATH}` binds only. The model behind it may be right — userdata is the household's own files (a shared `media/` tree must never go with one app) — but then the dialog must say the files stay and where. **Needs (operator):** keep userdata on remove (and say so on the dialog), or delete an app's OWN userdata subfolder when the household ticks the box. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator (the rule), CC (the dialog)** |
|
||||
| **R-801** | **[P2-MEDIUM] The volume-persistence gate never sent a request to ANY app: it read the routed port from label VALUES while `docker compose config --format json` puts the port in the label NAME.** MEASURED 2026-10-02 on the bench (`audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt`: `"ports": []`, `"exercise": []` for MeTube; the label shape read on Compose 2.26.1). So every verdict since the gate exists came only from what an app writes at start by itself; the GET exercise and the deep second pass never ran. **Fixed in the catalog** (`routed_ports()`, key=value; unit tests, red-proofed: `audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt`). **Still owed:** a full re-sweep of all 58 templates on the bench with the fixed gate — an app whose data path is written only on a request may now read differently. | **READY — rank P2-MEDIUM; owner: CC (the re-sweep)** |
|
||||
|
||||
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
|
||||
|
||||
Reference in New Issue
Block a user