Grimmory tested but held (R-775): its fixture added; CHANGELOG, CONTEXT, REPORT for the night's new apps
gates / gates (push) Successful in 2s
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
## Grimmory — tested, NOT published; fixture added (2026-10-01, night)
|
||||||
|
|
||||||
|
- Grimmory (the BookLore successor) passed the checklist on the bench and 9202 except one finding that needs the
|
||||||
|
operator: a stranger's 5 wrong sign-ins lock EVERY visitor out for 15 minutes (hard-coded; one address behind the
|
||||||
|
tunnel) — `felhom.eu` R-775. The template is NOT in `templates/`; it waits in
|
||||||
|
`felhom.eu/documentation/audits/new-apps-2026-10-01/wip/grimmory/`. Its fixture `Grimmory` (first admin, a library, an
|
||||||
|
uploaded EPUB, three negative controls) is in `upgrade_fixtures_box.py`, so the publishing session reuses it.
|
||||||
|
|
||||||
## Dawarich — the third app through the new-app checklist (2026-10-01, evening)
|
## Dawarich — the third app through the new-app checklist (2026-10-01, evening)
|
||||||
|
|
||||||
- **`templates/dawarich/`** — your own location history (instead of Google Timeline): `freikin/dawarich:1.15.3` (web +
|
- **`templates/dawarich/`** — your own location history (instead of Google Timeline): `freikin/dawarich:1.15.3` (web +
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
> Created with the REUSE.md rollout (2026-07-03). History: `CHANGELOG.md`; format spec: `README.md`.
|
> Created with the REUSE.md rollout (2026-07-03). History: `CHANGELOG.md`; format spec: `README.md`.
|
||||||
|
|
||||||
|
- **2026-10-01 (night) — FIRST NEW APPS through the checklist:** Radicale `195129c`, Karakeep `882ac14`, Dawarich `72247a3`
|
||||||
|
published with complete records; Grimmory tested but held (R-775, operator); Grimoire/MeTube/Pinchflat stopped on the
|
||||||
|
fit check; Invidious/moonlight-web fit only (`felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md`).
|
||||||
- **Ruling 2026-10-01 (operator) — wger HIDDEN** (`lifecycle: hidden`) until R-762 and R-763 are fixed; the fix session
|
- **Ruling 2026-10-01 (operator) — wger HIDDEN** (`lifecycle: hidden`) until R-762 and R-763 are fixed; the fix session
|
||||||
un-hides it.
|
un-hides it.
|
||||||
- **2026-10-01 (evening) — THE NEW-APP CHECKLIST.** `NEW-APP-CHECKLIST.md` (60 rows, `since` per id) + `onboarding/`
|
- **2026-10-01 (evening) — THE NEW-APP CHECKLIST.** `NEW-APP-CHECKLIST.md` (60 rows, `since` per id) + `onboarding/`
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
# REPORT — the new-app checklist: in the catalog, a gate, piloted on wger (2026-10-01, evening)
|
# REPORT — the first new apps through the checklist (2026-10-01, night)
|
||||||
|
|
||||||
Full session report: `felhom.eu/REPORT-new-app-checklist-2026-10-01.md`; evidence
|
Full session report: `felhom.eu/REPORT-new-apps-2026-10-01.md`; evidence `felhom.eu/documentation/audits/new-apps-2026-10-01/`
|
||||||
`felhom.eu/documentation/audits/new-app-checklist-2026-10-01/`.
|
(fit table `FIT.md`).
|
||||||
|
|
||||||
- **`NEW-APP-CHECKLIST.md`** reviewed: 60 rows in 10 groups (draft 53); 7 added, 16 sharpened, 9 wrong claims fixed.
|
- **wger hidden** (`lifecycle: hidden`, `55b8c8a`) until R-762 and R-763 are fixed; read back on 9202: not on the app list.
|
||||||
- **`onboarding/_TEMPLATE.md`** (one line per id) and **`onboarding/wger.md`** (the pilot; 11 open rows, each a register row).
|
- **Published, each with a complete `onboarding/<app>.md` and its first proven ladder step (bench swap 0 + 9202):**
|
||||||
- **Gate `onboarding`** (`scripts/check-onboarding.py`, `--fast`: hook + CI). The 53 published apps are exempt by name.
|
- **Radicale** (`195129c`) — CalDAV/CardDAV, upstream image; the login file is written on the FIRST start only
|
||||||
16 decoys judged right; 5 deliberately broken versions of the gate each turned the decoy suite red.
|
(R-765: rewritten at every start, a restore after a remove locked the household out — fixed before publishing).
|
||||||
- **`onboarding/EXISTING-APPS-GAPS.md`** from `scripts/onboarding_gaps.py` (read only).
|
- **Karakeep** (`882ac14`) — bookmarks + crawler (Chrome) + search; AI off unless the household enters a key; setup gate,
|
||||||
- **The pilot:** the draft caught R-752 and R-755 as written; it missed R-737 (its "how" logged in on the web form only)
|
sign-up closed twice; web memory raised to 1536M on measurements.
|
||||||
and R-738 for a NEW app (nothing to update at the newest tag). Rows 1.4/1.6 sharpened, 1.7/3.9 added — now all four.
|
- **Dawarich** (`72247a3`) — location history, PostGIS 17; the seeded known login replaced by `after_install` behind the
|
||||||
The new rows then found three live wger defects: R-762, R-763, R-764. No template was changed.
|
install hold; `SECRET_KEY_BASE` a data_key.
|
||||||
- Gates: `catalog_gates.py --fast` OK (11); `test_gate_decoys.py` 121 OK; `test_catalog_gates.py` OK;
|
- **Grimmory not published:** a stranger's 5 wrong sign-ins lock every visitor out for 15 min (hard-coded, one address
|
||||||
`decoy_coverage_gate.py` 0 unaccounted.
|
behind the tunnel) — R-775, the operator's word. Its template waits in the evidence `wip/grimmory/`; its fixture
|
||||||
|
(`Grimmory`) is in `upgrade_fixtures_box.py`.
|
||||||
|
- **Not built on the fit check:** Grimoire, MeTube, Pinchflat; Invidious and moonlight-web fit only.
|
||||||
|
- Fixtures added: `Radicale`, `Karakeep`, `Dawarich`, `Grimmory`. Gates: `catalog_gates.py --fast` OK on every push; the
|
||||||
|
runtime volume gate CLEAN for each published app (bench).
|
||||||
|
|||||||
@@ -2066,6 +2066,99 @@ class Dawarich:
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================================
|
||||||
|
class Grimmory:
|
||||||
|
"""Grimmory (2026-10-01, new app through NEW-APP-CHECKLIST.md). THE FRONT DOOR is the app's own API, the one its web
|
||||||
|
client calls: the first admin by `POST /api/v1/setup` (through the setup gate on the box, as the household), sign in
|
||||||
|
with `POST /api/v1/auth/login` (a bearer token), a library on `/books`, and a real EPUB (built here, with a unique
|
||||||
|
title) uploaded with `POST /api/v1/files/upload` — the app writes it into the household's book folder. Read back:
|
||||||
|
`GET /api/v1/books` lists the title. Negative controls on every readback: no token 401, a wrong password 401, and a
|
||||||
|
second `POST /api/v1/setup` refused (403) — the readback is not an open door."""
|
||||||
|
sub = "library"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _epub(title):
|
||||||
|
import io as _io, zipfile
|
||||||
|
b = _io.BytesIO()
|
||||||
|
z = zipfile.ZipFile(b, "w")
|
||||||
|
z.writestr(zipfile.ZipInfo("mimetype"), "application/epub+zip")
|
||||||
|
z.writestr("META-INF/container.xml", '<?xml version="1.0"?><container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')
|
||||||
|
z.writestr("OEBPS/content.opf", f'<?xml version="1.0"?><package xmlns="http://www.idpf.org/2007/opf" unique-identifier="id" version="2.0"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>{title}</dc:title><dc:creator>Felhom</dc:creator><dc:identifier id="id">{title}</dc:identifier><dc:language>hu</dc:language></metadata><manifest><item id="c1" href="c1.xhtml" media-type="application/xhtml+xml"/></manifest><spine><itemref idref="c1"/></spine></package>')
|
||||||
|
z.writestr("OEBPS/c1.xhtml", '<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><head><title>c</title></head><body><p>Szia!</p></body></html>')
|
||||||
|
z.close()
|
||||||
|
return b.getvalue()
|
||||||
|
|
||||||
|
def _token(self, w, sub, user, pw):
|
||||||
|
rc, code, out = w.app_curl(sub, "/api/v1/auth/login", "-H", "Content-Type: application/json",
|
||||||
|
data=json.dumps({"username": user, "password": pw}), method="POST")
|
||||||
|
try:
|
||||||
|
return code, json.loads(out).get("accessToken")
|
||||||
|
except Exception:
|
||||||
|
return code, None
|
||||||
|
|
||||||
|
def seed(self, w, sub, say):
|
||||||
|
import tempfile
|
||||||
|
if not w.wait_app(sub, "/api/v1/healthcheck", want=("200",), tries=90):
|
||||||
|
self.tried = "/api/v1/healthcheck never answered 200"
|
||||||
|
return None
|
||||||
|
user, pw = "admin", "Gm-" + secrets.token_hex(10)
|
||||||
|
rc, code, out = w.app_curl(sub, "/api/v1/setup", "-H", "Content-Type: application/json",
|
||||||
|
data=json.dumps({"username": user, "password": pw, "email": "admin@felhom.invalid", "name": "Felhom"}), method="POST")
|
||||||
|
say(f" grimmory: POST /api/v1/setup (the first admin) http={code}")
|
||||||
|
if code != "200":
|
||||||
|
self.tried = f"setup -> {code} {(out or '')[:120]}"
|
||||||
|
return None
|
||||||
|
code, tok = self._token(w, sub, user, pw)
|
||||||
|
if not tok:
|
||||||
|
self.tried = f"login -> {code}"
|
||||||
|
return None
|
||||||
|
auth = ["-H", f"Authorization: Bearer {tok}"]
|
||||||
|
rc, code, out = w.app_curl(sub, "/api/v1/libraries", *auth, "-H", "Content-Type: application/json",
|
||||||
|
data=json.dumps({"name": "Konyvek", "icon": "book", "iconType": "LUCIDE", "paths": [{"path": "/books"}],
|
||||||
|
"watch": True, "metadataSource": "EMBEDDED", "organizationMode": "BOOK_PER_FILE"}), method="POST")
|
||||||
|
try:
|
||||||
|
lib = json.loads(out)
|
||||||
|
lid, pid = lib["id"], lib["paths"][0]["id"]
|
||||||
|
except Exception:
|
||||||
|
self.tried = f"create library -> {code} {(out or '')[:120]}"
|
||||||
|
return None
|
||||||
|
title = "Felhom Teszt " + secrets.token_hex(4)
|
||||||
|
fn = tempfile.mktemp(suffix=".epub")
|
||||||
|
open(fn, "wb").write(self._epub(title))
|
||||||
|
if hasattr(w, "put_file"):
|
||||||
|
fn = w.put_file(fn)
|
||||||
|
rc, code, out = w.app_curl(sub, "/api/v1/files/upload", *auth, "-F", f"file=@{fn};type=application/epub+zip",
|
||||||
|
"-F", f"libraryId={lid}", "-F", f"pathId={pid}", method="POST")
|
||||||
|
say(f" grimmory: library {lid}, upload of an EPUB http={code}")
|
||||||
|
if code not in ("200", "201", "204"):
|
||||||
|
self.tried = f"upload -> {code} {(out or '')[:120]}"
|
||||||
|
return None
|
||||||
|
return {"user": user, "pw": pw, "title": title}
|
||||||
|
|
||||||
|
def verify(self, w, sub, t, say):
|
||||||
|
if not w.wait_app(sub, "/api/v1/healthcheck", want=("200",), tries=90):
|
||||||
|
say(" grimmory: /api/v1/healthcheck never answered")
|
||||||
|
return False
|
||||||
|
rc, c_none, _ = w.app_curl(sub, "/api/v1/books")
|
||||||
|
c_wrong, _ = self._token(w, sub, t["user"], t["pw"] + "x")
|
||||||
|
rc, c_setup, _ = w.app_curl(sub, "/api/v1/setup", "-H", "Content-Type: application/json",
|
||||||
|
data=json.dumps({"username": "x" + secrets.token_hex(2), "password": "Gm-" + secrets.token_hex(8),
|
||||||
|
"email": "x@felhom.invalid", "name": "X"}), method="POST")
|
||||||
|
if c_none != "401" or c_wrong != "401" or c_setup != "403":
|
||||||
|
say(f" grimmory: READBACK UNUSABLE — no token {c_none}, wrong password {c_wrong}, second setup {c_setup}")
|
||||||
|
return False
|
||||||
|
code, tok = self._token(w, sub, t["user"], t["pw"])
|
||||||
|
found = False
|
||||||
|
for _ in range(12):
|
||||||
|
rc, code, out = w.app_curl(sub, "/api/v1/books", "-H", f"Authorization: Bearer {tok}")
|
||||||
|
found = code == "200" and t["title"] in (out or "")
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
time.sleep(5)
|
||||||
|
say(f" grimmory: readback http={code} found={found} (controls: no token {c_none}, wrong {c_wrong}, second setup {c_setup})")
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
FIXTURES = {
|
FIXTURES = {
|
||||||
"uptime-kuma": UptimeKuma(),
|
"uptime-kuma": UptimeKuma(),
|
||||||
"crafty-controller": Crafty(),
|
"crafty-controller": Crafty(),
|
||||||
@@ -2105,4 +2198,5 @@ FIXTURES = {
|
|||||||
"radicale": Radicale(),
|
"radicale": Radicale(),
|
||||||
"karakeep": Karakeep(),
|
"karakeep": Karakeep(),
|
||||||
"dawarich": Dawarich(),
|
"dawarich": Dawarich(),
|
||||||
|
"grimmory": Grimmory(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user