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).
This commit is contained in:
2026-06-15 19:32:14 +00:00
parent 2f2225e466
commit 24b3a25ce6
2 changed files with 185 additions and 0 deletions

View File

@ -147,4 +147,11 @@ phases = [
{ id = "ghost", plan = "plan-phase-ghost-reeval.md", status = "STATUS-ghost.md" },
{ id = "cf48", plan = "plan-phase-cf48-opus-cfold-review.md", status = "STATUS-cf48.md", models = { builder = "claude-opus-4-8", adversary = "claude-opus-4-8" } },
{ id = "pxgate", plan = "plan-phase-pxgate-proxy-healthgate.md", status = "STATUS-pxgate.md" },
# agent-orchestrator build (builder=opus, adversary=sonnet) — see plan-agent-orchestrator.md
{ id = "aoeng", plan = "plan-phase-aoeng-engine.md", status = "STATUS-aoeng.md", models = { builder = "claude-opus-4-8", adversary = "claude-sonnet-4-6" } },
{ id = "aotest", plan = "plan-phase-aotest-verify.md", status = "STATUS-aotest.md", models = { builder = "claude-opus-4-8", adversary = "claude-sonnet-4-6" } },
{ id = "porepo", plan = "plan-phase-porepo-project-orchestrator.md", status = "STATUS-porepo.md", models = { builder = "claude-opus-4-8", adversary = "claude-sonnet-4-6" } },
{ id = "poe2e", plan = "plan-phase-poe2e-end-to-end.md", status = "STATUS-poe2e.md", models = { builder = "claude-opus-4-8", adversary = "claude-sonnet-4-6" } },
# gitea full-test enrollment + LFS PR #1 verification — see plan-phase-gtea-gitea-fulltests.md (operator 2026-06-15)
{ id = "gtea", plan = "plan-phase-gtea-gitea-fulltests.md", status = "STATUS-gtea.md", models = { builder = "claude-sonnet-4-6", adversary = "claude-sonnet-4-6" } },
]

View File

@ -0,0 +1,178 @@
# 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.