From 18577336f048af7cc7ba17c9aa2ee1c4037db4e5 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Fri, 29 May 2026 22:33:43 +0100 Subject: [PATCH] =?UTF-8?q?docs(2):=20Q5.1=20=E2=80=94=20enroll-recipe.md?= =?UTF-8?q?=20=C2=A72.4=20non-HTTP/multi-service/host-dependent=20recipes?= =?UTF-8?q?=20+=20mumble/mailu=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the Phase-2 Q4 patterns proven this session: EXTRA_ENV callable, READY_PROBE (HTTP+TCP), CHAOS_BASE_DEPLOY, recipe_checkout -f, install_steps overlay-drop; non-HTTP protocol tests (mumble host-ports + _mumble_proto), in-container functional tests (mailu flask/sendmail/doveadm under TLS_FLAVOR=notls), and P4-N/A when a recipe ships no backupbot label. Worked-example pointers to tests/mumble + tests/mailu. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/enroll-recipe.md | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/enroll-recipe.md b/docs/enroll-recipe.md index 94ae064..f9b4526 100644 --- a/docs/enroll-recipe.md +++ b/docs/enroll-recipe.md @@ -139,6 +139,52 @@ keycloak-specific; when authentik comes online a parallel `setup_authentik_realm land in `harness.sso`. The flow primitives (`oidc_password_grant`, `assert_discovery_endpoint`) ARE provider-pluggable. +### 2.4 Non-HTTP, multi-service, and host-dependent recipes (Phase 2 Q4) + +Not every recipe is a single HTTP app. `recipe_meta.py` + a few harness mechanisms cover the harder +shapes (proven on mumble, mailu, and the SSO-dependent suite): + +- **`EXTRA_ENV`** — a dict **or** a `callable(domain) -> dict`. The callable form derives values from + the per-run domain (e.g. `MAIL_DOMAIN`/`HOSTNAMES` for mailu, `SANDBOX_DOMAIN` for cryptpad). Applied + at every deploy (`abra.env_set`), so a recipe enrolls with NO shared-harness change. +- **`READY_PROBE(domain) -> [...]`** — readiness signals beyond replica-convergence + the app's + `HEALTH_PATH`. Two probe shapes: + - HTTP: `{"host": "...", "path": "/...", "ok": (200,)}` (e.g. lasuite-drive collabora WOPI discovery). + - **TCP**: `{"tcp_host": "127.0.0.1", "tcp_port": 64738, "stable": 3}` — polls a socket connect N + consecutive times. Use for non-HTTP services whose `HEALTH_PATH` reflects a sidecar, not the real + service (mumble: the mumble-web sidecar serves HTTP 200 while the voice server on 64738 is still + rebinding after an upgrade redeploy — the TCP probe gates the backup tier until the voice server is + actually up). Runs after install AND after the upgrade chaos redeploy. +- **`CHAOS_BASE_DEPLOY = True`** — make the pinned base deploy use `--chaos` (skips abra's clean-tree + + lint gates, still deploys the explicitly-checked-out pinned version, NOT latest). Needed when an + `install_steps.sh` adds an UNTRACKED file to the recipe checkout (e.g. mumble copies a + `compose.host-ports.yml` into versions that predate it) — abra's pinned-deploy clean-tree check would + otherwise FATA. `abra.recipe_checkout` force-checks-out (`-f`) so the upgrade tier's re-checkout to + PR-head overwrites such overlays cleanly. +- **`install_steps.sh`** (auto-discovered at `tests//install_steps.sh`) — runs after + `abra app new` + EXTRA_ENV + secret-generate, BEFORE the single deploy, with `CCCI_APP_DOMAIN` / + `CCCI_APP_ENV` / `CCCI_RECIPE` (and `CCCI_DEPS_FILE` when DEPS are provisioned at install). Use it to + drop a cc-ci-owned compose overlay into the checkout, wire dep-derived env/secrets, etc. + +**Non-HTTP protocol tests (mumble).** Reach a TCP service published `mode: host` (via a host-ports +overlay) at `127.0.0.1:` — cc-ci runs tests on-host (cc-ci-run). mumble ships a stdlib protocol +client (`tests/mumble/functional/_mumble_proto.py`) doing the real TLS handshake → ServerSync; the +recipe-specific tests assert channel presence and config round-trips (a deploy-set `WELCOME_TEXT`/ +`USERS` value surfaces over the protocol — version-independent, non-vacuous). + +**In-container functional tests (mailu).** When network access to a service is constrained (mailu uses +`TLS_FLAVOR=notls` because certdumper needs traefik ACME which cc-ci does not run → dovecot refuses +plaintext auth over the network), exercise the app via `lifecycle.exec_in_app(domain, [...], +service="")` against the relevant container: e.g. `flask mailu user ...` (admin) to create a +mailbox, then a local `sendmail` inject (smtp) → `doveadm search` (imap) to prove real +postfix→rspamd→dovecot delivery. This hits the same stack the network path would, without the env +constraint. + +**P4 when the recipe ships no backup (`backupbot`) labels.** `generic.backup_capable` auto-detects the +`backupbot.backup` label; recipes without it (mailu, drone) cleanly SKIP the backup/restore tiers — +P4 is genuinely N/A (nothing to back up), not a cut corner. Document it in `PARITY.md` + a `DEFERRED.md` +entry (the durable fix is a backupbot recipe-PR, like immich), and seek Adversary §7.1 sign-off. + ## 3. Recipe-local tests (D4) — default-deny (HC2) If the recipe's own repo contains `tests/test_*.py` / `install_steps.sh` / `ops.py`, the runner @@ -204,3 +250,16 @@ tests/lasuite-docs/ 4. Teardown lasuite-docs, then the keycloak dep (LAST), both with verify=True. 5. Print the run summary; non-zero exit code on any failure (DG4.1 deploy-count mismatch, tier FAIL, dep teardown leak — all surfaced). + +### Other shapes (concrete references) + +- **TCP / voice recipe — `tests/mumble/`**: `recipe_meta.py` (EXTRA_ENV sets + `COMPOSE_FILE=compose.yml:compose.mumbleweb.yml:compose.host-ports.yml`, `WELCOME_TEXT`/`USERS` + markers, `CHAOS_BASE_DEPLOY=True`, `READY_PROBE` TCP 64738), `install_steps.sh` (provides the + host-ports overlay to older versions), `functional/_mumble_proto.py` + the protocol/config-round-trip + tests, `ops.py`/`test_backup.py`/`test_restore.py` (sqlite P4). See §2.4. +- **Multi-service, dep-less, in-container functional — `tests/mailu/`**: `recipe_meta.py` + (`EXTRA_ENV(domain)` with `TLS_FLAVOR=notls` + `MAIL_DOMAIN`/`HOSTNAMES`/`TRAEFIK_STACK_NAME`), + `functional/_mailu.py` (flask-CLI helpers), `test_mailbox.py` (create→config-export read-back), + `test_mail_flow.py` (in-container sendmail→doveadm delivery). No backupbot → P4 N/A (PARITY.md + + DEFERRED.md). See §2.4.