Files
cc-ci-orchestrator/cc-ci-plan/plan-phase-gtea-gitea-fulltests.md
autonomic-bot 24b3a25ce6 plan: queue gtea — enroll gitea as a fully-tested recipe + verify LFS PR #1
Operator-requested 2026-06-15. gitea is currently only a dep provider for
drone; this phase builds the full recipe-under-test suite (install/upgrade/
backup/restore/custom + lint + screenshot), ports the upstream parity corpus
(health_check, git_push), and verifies recipe-maintainers/gitea PR #1
('feat: support Git LFS on plain gitea', branch lfs-plain-gitea) via an LFS
round-trip + JWT-stability capstone test — red/skipped on main, green on the
PR. Central constraint: do NOT break drone's gitea-dep path (shared
recipe_meta.py). builder+adversary on sonnet.

The agents.toml diff also records the already-live aoeng/aotest/porepo/poe2e
queue head (agent-orchestrator workstream; their plan files remain owned by
that effort).
2026-06-15 19:32:14 +00:00

179 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase `gtea` — enroll gitea as a FULLY-TESTED recipe (+ verify LFS PR #1)
**Mission (operator-specified 2026-06-15):** gitea currently exists in cc-ci only as a
**dependency provider** for the `drone` phase — `tests/gitea/recipe_meta.py` is the lone
file and its header says "NOT a standalone recipe-under-test". Turn gitea into a
**fully-tested recipe** (install + upgrade + backup + restore + custom functional tests +
lint + screenshot), to the same standard as `cryptpad`/`keycloak`. **Then, once the suite
is basically working, verify it against the open LFS PR**
`https://git.autonomic.zone/recipe-maintainers/gitea/pulls/1` (`feat: support Git LFS on
plain gitea`, branch `lfs-plain-gitea``main`) — the suite's LFS capstone test is the
mechanism that proves the PR.
**The non-negotiable constraint that shapes everything below: do NOT break drone.** gitea's
`recipe_meta.py` is loaded by the SAME code path whether gitea is drone's dep or the
recipe-under-test. Drone's CI must stay green throughout (it provisions gitea as an
install-time dep, sqlite3 + relaxed auth, via `sso.setup_gitea_oauth`).
State files: `STATUS-gtea.md`, `BACKLOG-gtea.md`, `REVIEW-gtea.md`, `JOURNAL-gtea.md`.
DECISIONS.md shared.
## 0. Prerequisites (verify before deploying)
- **`/etc/timezone` host fix is already live** (shipped with the `drone` phase — gitea binds
`/etc/timezone:ro`; the Nix `environment.etc."timezone"` fix is deployed). Builder's first
action: confirm `test -f /etc/timezone` on the cc-ci host (content `UTC`). It should exist
— drone already deploys gitea. If it's somehow absent, write a BLOCKED note in
STATUS-gtea.md ("P0 host deploy needed — orchestrator") and do meta/test authoring that
needs no deploy until the orchestrator restores it.
- abra over a pseudo-TTY (`ssh cc-ci 'script -qec "abra … -n" /dev/null'`); creds baked into
the mirror origin where needed; stash the untracked overlay. CI host has no python3 on the
default PATH.
## 1. Scope — the file manifest for `tests/gitea/`
Build to the recipe-under-test anatomy (registry/contracts: `runner/harness/meta.py`,
`runner/harness/discovery.py`; exemplars `tests/cryptpad/`, `tests/keycloak/`):
1. **`recipe_meta.py` (UPDATE — carefully; this is the conflict surface).**
- Keep the EXISTING dep behaviour intact (sqlite3 `COMPOSE_FILE`, relaxed-auth env) so
drone's gitea-dep deploy is byte-for-byte unchanged. Update the header comment: gitea is
now **both** a dep provider AND a recipe-under-test.
- Add the recipe-under-test keys that are harmless to the dep path: `HEALTH_PATH`
(`/api/healthz` already set), `HEALTH_OK`, sensible `DEPLOY_TIMEOUT`/`HTTP_TIMEOUT`,
`BACKUP_CAPABLE` (the gitea recipe HAS `backupbot.backup` labels — backup/restore are
REAL tiers, not skips), and `SCREENSHOT` (gitea has a real landing/login UI — default
capture should work; follow the `shot`-phase standard). Consider `READY_PROBE` for a
gitea-specific readiness gate (API reachable, not just TCP).
- **DB:** sqlite3 (matches the dep config + lightest footprint; gitea ships
`compose.sqlite3.yml`). Do not switch the dep to mariadb/postgres.
- **No new ALL-CAPS meta keys** without adding them to the `meta.py` registry + regenerating
docs (unknown caps = hard MetaError, unit-tested).
2. **`ops.py` (NEW)** — uniform `HookCtx` hooks (`pre_backup`/`pre_restore`/`pre_upgrade`,
and `pre_install` if needed for the LFS secret — see §3). Seed a **data-integrity marker**
via the gitea admin REST API: create a repo (+ a user/org and a committed file) before the
op so `test_backup`/`test_restore`/`test_upgrade` can assert data continuity. Model on
`tests/keycloak/ops.py` (API-based markers) — NOT file-in-container hacks.
3. **Lifecycle overlays (NEW)** — additive on the `_generic` floor:
- `test_install.py` — generic `assert_serving()` + gitea-specific: `/api/healthz` → 200 and
the admin API is reachable/authenticated.
- `test_backup.py` — the seeded repo/user marker is present after backup.
- `test_restore.py`**mutation pattern**: `ops.pre_restore` deletes/mutates the marker;
restore must return it to the backed-up state; assert reverted (real divergence — a
no-op restore must fail this).
- `test_upgrade.py` — the marker survives the upgrade to PR-head (data continuity across
the chaos redeploy).
4. **`custom/` functional tests (NEW)** — discovered from `tests/gitea/custom/test_*.py`.
Floor is ≥2 tests **beyond** parity. Deliver:
- `custom/test_health.py`**parity port** of `recipe-info/gitea/tests/health_check.py`.
- `custom/test_git_push.py`**parity port** of `recipe-info/gitea/tests/git_push.py`:
create repo via API → clone → commit → push over HTTPS → verify commit landed via API →
clean up. (Has real teeth: a broken SCM fails the push.)
- `custom/test_admin_api.py` — beyond-parity: user + org + token lifecycle via the REST API
(create, read-back, delete), proving admin-API CRUD works.
- `custom/test_lfs_roundtrip.py` — beyond-parity **and the PR-#1 capstone** (see §3).
5. **`PARITY.md` (NEW)** — map the upstream corpus (`recipe-info/gitea/tests/health_check.py`,
`git_push.py`) to the cc-ci ports; document the beyond-parity custom tests + why they're
non-vacuous; record that backup/restore are REAL tiers (backupbot labels present); record
the sqlite3 DB choice; declare any deferrals with reasons (`EXPECTED_NA` for anything that
structurally can't run on `main`, e.g. LFS before PR #1 merges — see §3).
## 2. The gitea-dep conflict — the central design problem (READ FIRST)
`meta_mod.load("gitea")` returns ONE object used by BOTH roles. The two roles never run
concurrently (different `RECIPE` values: `RECIPE=drone` provisions gitea as a dep;
`RECIPE=gitea` is the recipe-under-test), but they SHARE `recipe_meta.py`. Therefore:
- **Hard gate: drone CI stays green.** After any `recipe_meta.py` change, run drone through
its CI/`!testme` path and confirm GREEN — gitea-as-dep provisioning (`sso.setup_gitea_oauth`,
`tests/unit/test_gitea_dep.py`) must be unaffected. Treat a drone regression as a failed
gate, never "acceptable collateral".
- **Do not let recipe-under-test config leak into the dep deploy.** In particular the LFS
overlay (§3) must apply ONLY when gitea is the recipe-under-test, never to drone's dep
deploy. Investigate whether `EXTRA_ENV(ctx)` can distinguish the roles (e.g. via `ctx.op` /
`ctx.deps` / domain shape) or whether the overlay belongs on a recipe-under-test-only knob.
**Decide, document the mechanism in PARITY.md + DECISIONS.md, and prove both paths.**
- If a clean split proves impossible inside one `recipe_meta.py`, raise it in STATUS before
forking any shared harness behaviour — do not silently special-case gitea in `runner/`.
## 3. Verify the LFS PR (#1) — the capstone
PR #1 (`lfs-plain-gitea``main`) adds **opt-in** Git LFS to plain gitea: a new
`compose.lfs.yml` overlay (`GITEA_LFS_START_SERVER=true` + a mounted `lfs_jwt_secret`), an
`app.ini.tmpl` change emitting a stable `LFS_JWT_SECRET`, `.env.sample` docs, and a version
bump `3.5.2 → 3.6.0`. The PR's own "Tested on cctest" evidence is exactly the test to encode:
**an LFS object upload→download round-trip via the batch API (downloaded bytes hash to the
OID), plus the LFS JWT secret stable across `abra app restart`** (the regression it fixes).
- `custom/test_lfs_roundtrip.py` enables LFS (`COMPOSE_FILE` includes `compose.lfs.yml`;
generate the `lfs_jwt_secret` via `abra app secret generate … lfs_jwt_secret v1` +
`SECRET_LFS_JWT_SECRET_VERSION=v1`, length 43 — wire through `EXTRA_ENV`/`ops.pre_install`
for the recipe-under-test deploy only, never the drone dep), then: init a repo with
`git lfs`, push an LFS-tracked object, fetch it from a clean clone, assert the OID hash
matches; then `abra app restart` and assert the rendered `app.ini` `LFS_JWT_SECRET` is
unchanged and tokens still validate.
- **Because `compose.lfs.yml` is NEW in PR #1, this is the proof the suite verifies the PR:**
on gitea `main` the overlay is absent → the LFS test **structurally skips** (declare it in
`EXPECTED_NA` with reason "LFS overlay lands in PR #1"); on the `lfs-plain-gitea` head the
overlay exists → the LFS test **runs and must pass**. Red/absent on main, green on the PR.
- **How to verify against the PR:** once the suite is green on `main`, run the harness with the
PR head checked out — the `ci-test-review`/`recipe-upgrade` PR path (`RECIPE=gitea
REF=lfs-plain-gitea` via `verify-pr.sh`), and/or post `!testme` on PR #1 so the result lands
in the PR for the operator. Full lifecycle (install/upgrade/backup/restore/custom) must stay
green on the PR head, AND the LFS capstone must go green there.
- **We VERIFY PR #1; we do NOT merge it** (operator's call — mirror PR-only rules). If the
verification surfaces a real defect in the PR, leave a PR comment per the `recipe-upgrade`
discipline; do not "fix" it by weakening the test.
## 4. Gates
**M1 — Suite built + green locally (on gitea `main`).** All chosen tiers green on the harness
path with evidence: install + upgrade + backup + restore + custom (`test_health`,
`test_git_push`, `test_admin_api`) + lint (L5) + screenshot. `recipe_meta.py` updated WITHOUT
breaking drone — **drone's gitea-dep deploy still green (proven, not assumed)**. The LFS test
correctly **skips** on `main` (overlay absent), declared in `EXPECTED_NA`. Unit tests for any
new harness-visible surface; no gate weakening anywhere. Adversary cold-verifies from a clean
checkout: tests have teeth (a misconfigured gitea fails install/health; `git_push` actually
pushes + verifies via API; the backup/restore mutation genuinely diverges then reverts);
declared skips justified against the published recipe; **no drone regression**; the
dep-vs-recipe-under-test split (§2) is real and documented.
**M2 — Proven in real CI + PR #1 verified.** Full gitea lifecycle green via the real CI /
`!testme` path on `main`; drone CI re-confirmed green (dep path intact); screenshot real +
visually verified; level recorded under the de-capped semantics; canonical/warm enrollment
decision documented. **Then the PR-#1 verification:** harness on `lfs-plain-gitea` head →
full lifecycle green AND the LFS round-trip + JWT-stability capstone GREEN (and demonstrably
red/skipped on `main`), result posted to PR #1; nothing merged. Operator summary in
STATUS-gtea.md. Fresh Adversary PASS on both milestones → `## DONE`.
## 5. Guardrails (binding)
- **Drone stays green — the dep path is sacred.** Never relax/weaken gitea's dep behaviour to
make the standalone suite pass. A drone regression fails the phase.
- **Recipe mirrors are PR-only.** Never push the mirror's `main`, never merge. PR #1 is the
operator's — we verify it (run the harness / post `!testme`), we do not merge it. A recipe
defect found → PR/comment per `recipe-upgrade`, operator decides.
- **Never weaken a test** to turn red green. Bounded changes (the enrollment + LFS capstone),
not harness rewrites. No new meta keys without registry + doc regeneration.
- **Secrets** (gitea admin password, OAuth client secret, `lfs_jwt_secret`) are generated
per-run and MUST stay out of logs/commits/artifacts — manifest redaction rules apply.
- **Shared swarm:** ≤23 concurrent deploys; one `dev-`/test gitea at a time; **tear down
every deploy on every exit path** (success, red, or abort), dep included. The drone path
brings up two deploys (gitea + drone) — budget for it.
- Host changes are orchestrator/operator-only (file them in STATUS, don't improvise). Commit
author `autonomic-bot <autonomic-bot@noreply.git.autonomic.zone>`; push every commit.
## 6. Definition of Done
gitea enrolled as a fully-tested recipe (install/upgrade/backup/restore + custom functional
tests + lint + screenshot) green in **real CI**, **without breaking drone's gitea-dep path**;
PARITY.md complete (upstream `health_check`/`git_push` ported, beyond-parity tests + LFS
documented); the LFS PR #1 (`lfs-plain-gitea`) **verified GREEN** via the LFS capstone
(round-trip + JWT-secret stability) on the PR head, with the same test demonstrably
red/skipped on `main` — proving the suite catches the feature; result posted to PR #1, nothing
merged; levels/records reconciled; M1 + M2 fresh Adversary PASSes recorded in REVIEW-gtea.md.