feat(2): ghost P4 data-integrity overlay (MySQL ci_marker) + §4.3 create-post round-trip

- ops.py + test_{upgrade,backup,restore}.py: seed ci_marker into the MySQL `ghost` DB (db service)
  via the mysql CLI; rides the recipe's mysqldump --tab backup. recipe is MySQL not sqlite (stale
  comment fixed). Expect restore RED -> recipe-PR (no backupbot.restore hook; immich/mattermost class).
- functional/_ghost.py: cookie-aware Ghost Admin API client (stdlib http.cookiejar; Origin CSRF hdr).
- functional/test_post_roundtrip.py: §4.3 create published post + read back (unique marker, non-vacuous);
  closes the DEFERRED ghost create-post item.
- PARITY.md + recipe_meta.py updated. Authored node-free; full-lifecycle run next, NOT yet claimed.
This commit is contained in:
2026-05-30 04:14:06 +01:00
parent c8c3cc8858
commit b4d03ccafe
8 changed files with 355 additions and 14 deletions

View File

@ -16,22 +16,35 @@ and a JSON Content/Admin API at `/ghost/api/*`. Defining behaviors exercised:
Two specific tests + parity health_check = ≥2 floor met.
## Plan §4.3 prescribed deeper test (deferred to Q4 follow-up)
## Plan §4.3 prescribed deeper test — AUTHORED (closes DEFERRED ghost create-post)
§4.3 named "create-a-post round-trip" for ghost. That requires:
1. Setup the Ghost owner (POST `/ghost/api/v3/admin/authentication/setup/`) with a per-run
admin email+password.
2. Login → JWT bearer token.
3. POST `/ghost/api/v3/admin/posts/` to create a post.
4. GET `/ghost/api/v3/admin/posts/<id>/` to read it back.
§4.3 named "create-a-post round-trip" for ghost. Implemented in
`tests/ghost/functional/test_post_roundtrip.py` (helper `functional/_ghost.py`):
1. Wait for the Admin API healthcheck (`GET /ghost/api/admin/site/` → 200).
2. Setup the Ghost owner (POST `/ghost/api/admin/authentication/setup/`, fresh deploy) + establish
an admin **session cookie** (POST `/ghost/api/admin/session/`) — cookie-aware stdlib opener,
version-negotiated (no `/v3/` in the path; recipe-versioned).
3. POST `/ghost/api/admin/posts/?source=html` to create a published post with a unique marker in
title + body.
4. GET `/ghost/api/admin/posts/<id>/?formats=html` to read it back; assert title + body marker
round-trip intact (unique-per-run → non-vacuous).
Doable; adds a per-run setup secret + token-management. Tracked for Q4 follow-up.
Admin creds are class-B run-scoped (destroyed at teardown with the app).
## Backup data-integrity (P4)
## Backup data-integrity (P4) — AUTHORED
Lifecycle overlays not authored. The base recipe stores state in SQLite + a content volume;
backup-capable is auto-detected from compose. Q5 catch-up if backup data-integrity proves
needed for this recipe.
`ops.py` + `test_install`-free lifecycle overlays (`test_upgrade.py` / `test_backup.py` /
`test_restore.py`) seed a deterministic `ci_marker` row into the **MySQL** `ghost` DB (the recipe's
real state store) via the `mysql` CLI in the `db` service. The recipe's backupbot pre-hook
(`mysqldump ghost --tab`) dumps that table into the backed-up path, so the marker rides
backup→restore the way a real post's row would. pre_restore drops the table (divergence); the
restore overlay asserts it returned.
**Expected RED until a recipe-PR lands:** the ghost recipe has a logical mysqldump backup but **no
`backupbot.restore.*` hook** (and the mysql data volume itself isn't backupbot-labelled), so a
file-level restore never reimports the dump — same defect class fixed in immich#1 / mattermost-lts#1.
If `test_restore_returns_state` goes RED, the durable fix is a recipe-PR adding a mysqldump-reimport
restore post-hook. (See `test_restore.py` docstring + DECISIONS.md.)
## Playwright (P6)