--- name: recipe-enroll description: Add a NEW recipe to cc-ci's maintained set, end to end — create + sync the recipe-maintainers mirror from coopcloud upstream, author a real test suite (health floor + non-vacuous recipe-specific tests incl. a create-an-object round-trip), enroll it in the !testme bridge (POLL_REPOS) and the weekly /upgrade-all inventory (used-recipes.md weekly row + upstream registry), then VERIFY the whole thing: full harness suite green with the new tests, bridge deployed (test-before-switch) and healthy. Opens the cc-ci PR for visibility and merges it directly once verification is green (the skill invocation is the authorization); the report lists merged PR links + what changed. Invoke as /recipe-enroll . --- # recipe-enroll Enroll a coopcloud recipe as a **maintained** recipe: mirrored, test-covered, `!testme`-triggerable, and picked up by the weekly `/upgrade-all`. This is the full end-to-end path — worked example: the 2026-08-03 wordpress enrollment (cc-ci PR #14). **"Maintained" = ALL of:** 1. Mirror `recipe-maintainers/` exists, `main` = coopcloud upstream main, tags synced. 2. `tests//` enrolled in the cc-ci repo (this is what `/upgrade-all` + `ci-test-review` enumerate). 3. `POLL_REPOS` in `nix/modules/bridge.nix` includes the mirror (the `!testme` bridge). 4. `cc-ci-plan/used-recipes.md` has a ` weekly` row (orchestrator repo — `/upgrade-all` skips recipes without it or tagged `external`). 5. `cc-ci-plan/upstream/.md` registry entry (release-notes URLs + standing notes). ## Preconditions - The recipe exists upstream: `ssh cc-ci 'script -qec "abra recipe fetch " /dev/null'` succeeds (every recipe must have a coop-cloud correspondent). - `GITEA_*` creds in `/srv/cc-ci/.testenv` (orchestrator side; the cc-ci host does NOT have this file — inject creds over stdin when running helper scripts there, see step 2). - The shared Swarm is quiescent for step 6 (verification deploys the recipe) — do NOT verify concurrent with `/upgrade-all` or other verify runs; author everything first, verify when clear. ## Procedure ### 1. Survey the recipe (read-only) On cc-ci, after `abra recipe fetch `, read `~/.abra/recipes//`: - `compose.yml` — services + images (for the upstream registry), healthcheck (`start_period` informs `DEPLOY_TIMEOUT`), traefik labels. - `.env.sample` — is the app self-initializing, or does a fresh deploy sit in a setup wizard (e.g. wordpress without `POST_DEPLOY_CMDS core_install`)? The tests must handle the state a fresh CI deploy actually lands in. - Auth model — how will a test create an object? (REST + token, session login, XML-RPC, …) Check overlay configs (htaccess/nginx templates) for blocked endpoints before relying on one. ### 2. Create + sync the mirror Create `recipe-maintainers/` (Gitea API: `POST /orgs/recipe-maintainers/repos`, `{"name":…,"private":true,"default_branch":"main","auto_init":false}`), then force-sync from coopcloud with the existing helper **run on cc-ci with creds injected via stdin** (the host has no `.testenv`): ``` set -a; . /srv/cc-ci/.testenv; set +a { printf 'export GITEA_USERNAME=%q GITEA_PASSWORD=%q GITEA_URL=%q\n' "$GITEA_USERNAME" "$GITEA_PASSWORD" "$GITEA_URL"; cat /srv/cc-ci-orch/.claude/skills/recipe-upgrade/open-recipe-pr.sh; } \ | ssh cc-ci 'bash -s -- --reconcile-only' ``` Expect: repo created (or exists), `main` force-synced to upstream, published tags pushed. ### 3. Author the test suite (dedicated cc-ci clone + branch) ``` git clone ssh://git@git.autonomic.zone:2222/recipe-maintainers/cc-ci.git /home/loops/work/cc-ci-tests- cd … && git checkout -b test/-enroll-$(date -u +%Y%m%d) ``` `tests//` contents (template: `tests/uptime-kuma/`, `tests/wordpress/`): - **`recipe_meta.py`** — `HEALTH_PATH` / `HEALTH_OK` (accept the fresh-deploy state, e.g. a 302 to a setup wizard), `DEPLOY_TIMEOUT` (recipe healthcheck `start_period` + DB init + first-boot copy, be generous), `HTTP_TIMEOUT`, `WARM_CANONICAL = True` (canon §2.B — all recipes enroll as data-warm canonicals, operator 2026-06-17). - **`custom/`** — the health floor + **≥2 recipe-specific, non-vacuous tests**, one of which is the §4.3 **create-an-object + read-it-back** (write through the app's real API, read back via a *different* path where possible — e.g. wordpress: XML-RPC write → REST read → permalink HTML). If the app needs setup/auth, put it in a recipe-local `_.py` helper (idempotent `ensure_*` so test ordering doesn't matter; run-scoped class-B credentials — the app is destroyed at teardown). Design assertions to name the broken layer (e.g. assert both the rewrite-dependent and rewrite-independent API routes separately). - **`PARITY.md`** — table of tests × what's verified × why non-vacuous; note there's no recipe-maintainer parity corpus if so. - Lint with the **repo dev-shell ruff**: `nix develop -c ruff check tests// && nix develop -c ruff format tests//` (pre-existing drift in other files is not yours). ### 4. Bridge enrollment (same branch) `nix/modules/bridge.nix`: append `,recipe-maintainers/` to the `POLL_REPOS=` CSV. ### 5. Inventory + registry (orchestrator repo, direct to main) - `cc-ci-plan/used-recipes.md`: add ` weekly` row (alphabetical). - `cc-ci-plan/upstream/.md`: images table (source repo + releases/changelog links) + standing notes (setup-wizard behavior, auth caveats, known upgrade traps). - Commit + push (doc commits go direct to main in this repo). ### 6. Verify end-to-end — full suite GREEN with the new tests (swarm-serialized) Open the cc-ci PR first (visibility): `TITLE=… BODY_FILE=… bash /srv/cc-ci-orch/.claude/skills/ci-test-review/open-cc-ci-pr.sh`. The body: what's enrolled, the test design rationale, the verify + deploy plan. Then, when the swarm is clear: ``` ssh cc-ci 'rm -rf /root/cc-ci-test-verify && git clone --branch test/-enroll- \ ssh://git@git.autonomic.zone:2222/recipe-maintainers/cc-ci.git /root/cc-ci-test-verify && \ cd /root/cc-ci-test-verify && git submodule update --init secrets 2>/dev/null || true' RECIPE= REMOTE_ROOT=/root/cc-ci-test-verify \ bash /srv/cc-ci-orch/.claude/skills/ci-test-review/verify-pr.sh # no REF — recipe main ``` Required: **cold full-suite green** (install/upgrade/backup/restore/custom). Iterate the tests (bounded, ≤3 attempts) if red — fix the TESTS to match real app behavior, never weaken. Clean up `/root/cc-ci-test-verify` after. ### 7. Deploy the bridge change to the cc-ci host (test-before-switch) The `POLL_REPOS` change only takes effect via a host rebuild. Per `/cc-ci-server-update` steps 5a-e: stage the branch to `/root/cc-ci-deploy` (+ secrets copy), `nixos-rebuild build`, detached `nixos-rebuild test`, verify reachable + healthy, then `switch`. Confirm the bridge is polling the new repo: `ssh cc-ci 'docker service inspect ccci-bridge_app --format "{{json .Spec.TaskTemplate.ContainerSpec.Env}}"' | grep ` and the bridge task is 1/1 with no auth errors in its logs (a stale Gitea secret 401s silently — see the 2026-08-03 finding). ### 8. Merge + report Merge the cc-ci PR (invocation = authorization; PR is the visible record — comment the verification evidence first). Report to the operator: merged PR link + change summary, the verify log path, and the note that the **next weekly `/upgrade-all` picks the recipe up automatically** (it enumerates `tests//` dirs × `weekly` rows). ## Guardrails - **Full-suite green is the enrollment gate** — an enrolled-but-red recipe poisons every future sweep. Don't merge on partial green. - **Never weaken**: the tests assert the app's real current behavior, incl. asserting removed auth paths are rejected where that's the upstream intent. - **Single-writer**: dedicated clones/branches; never push `main` of cc-ci; never touch `/root/builder-clone` or the loops' clones; `/root/cc-ci-test-verify` is yours — remove after. - **Serialize on the swarm**: authoring is free, verification + bridge deploy wait for `/upgrade-all`/other runs to finish. - **abra over ssh needs the pseudo-TTY wrap**: `ssh cc-ci 'script -qec "abra …" /dev/null'`. - **PRs for visibility, merged directly once verified**; failed enrollment leaves the PR open with an explanatory comment and the report says exactly what's missing.