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).
12 KiB
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/timezonehost fix is already live (shipped with thedronephase — gitea binds/etc/timezone:ro; the Nixenvironment.etc."timezone"fix is deployed). Builder's first action: confirmtest -f /etc/timezoneon the cc-ci host (contentUTC). 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/):
-
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/healthzalready set),HEALTH_OK, sensibleDEPLOY_TIMEOUT/HTTP_TIMEOUT,BACKUP_CAPABLE(the gitea recipe HASbackupbot.backuplabels — backup/restore are REAL tiers, not skips), andSCREENSHOT(gitea has a real landing/login UI — default capture should work; follow theshot-phase standard). ConsiderREADY_PROBEfor 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.pyregistry + regenerating docs (unknown caps = hard MetaError, unit-tested).
- Keep the EXISTING dep behaviour intact (sqlite3
-
ops.py(NEW) — uniformHookCtxhooks (pre_backup/pre_restore/pre_upgrade, andpre_installif 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 sotest_backup/test_restore/test_upgradecan assert data continuity. Model ontests/keycloak/ops.py(API-based markers) — NOT file-in-container hacks. -
Lifecycle overlays (NEW) — additive on the
_genericfloor:test_install.py— genericassert_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_restoredeletes/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).
-
custom/functional tests (NEW) — discovered fromtests/gitea/custom/test_*.py. Floor is ≥2 tests beyond parity. Deliver:custom/test_health.py— parity port ofrecipe-info/gitea/tests/health_check.py.custom/test_git_push.py— parity port ofrecipe-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).
-
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_NAfor anything that structurally can't run onmain, 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.pychange, run drone through its CI/!testmepath 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. viactx.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 inrunner/.
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.pyenables LFS (COMPOSE_FILEincludescompose.lfs.yml; generate thelfs_jwt_secretviaabra app secret generate … lfs_jwt_secret v1+SECRET_LFS_JWT_SECRET_VERSION=v1, length 43 — wire throughEXTRA_ENV/ops.pre_installfor the recipe-under-test deploy only, never the drone dep), then: init a repo withgit lfs, push an LFS-tracked object, fetch it from a clean clone, assert the OID hash matches; thenabra app restartand assert the renderedapp.iniLFS_JWT_SECRETis unchanged and tokens still validate.- Because
compose.lfs.ymlis NEW in PR #1, this is the proof the suite verifies the PR: on giteamainthe overlay is absent → the LFS test structurally skips (declare it inEXPECTED_NAwith reason "LFS overlay lands in PR #1"); on thelfs-plain-giteahead 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 — theci-test-review/recipe-upgradePR path (RECIPE=gitea REF=lfs-plain-giteaviaverify-pr.sh), and/or post!testmeon 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-upgradediscipline; 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 perrecipe-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: ≤2–3 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.