Files
command-center/agent/managed-skills/new-service-deploy-gate-census/SKILL.md
T

43 lines
3.6 KiB
Markdown

---
name: new-service-deploy-gate-census
description: "When a newly added serverAppConfigs fleet service fails to deploy (\"is not deployed on target\" or \"Unit svc.service not found\"), run the full gate census; use pre-flight when adding any new service."
---
Adding a deployable service to serverAppConfigs touches SIX independent gates. PR #69 (blackbox-exporter, 2026-10-21) shipped with three of them missing and failed three separate deploys before all were found. Run this census pre-flight (new service) or as a debug ladder (deploy failure).
## The six gates, in failure order
| # | Gate | File | Failure signature |
|---|------|------|-------------------|
| 1 | client allow-list `valid_service()` | `scripts/deploy.sh` | `Service 'X' is not deployed on <target>.` (client-side, before anything runs) |
| 2 | remote allow-list `service_is_allowed()` | `scripts/deploy-remote.sh` | same message from the remote runner |
| 3 | dispatch case in `deploy_service()` | `scripts/deploy-remote.sh` | falls to catch-all `deploy_compose_service` which HARD-FAILS on dankclaw (no compose since 2026-09-16): `no quadlet handler` |
| 4 | planner entry + `SERVICE_ORDER` | `scripts/deploy-plan.py` | service silently absent from auto-plans (explicit `--service` still reaches gates 1-3) |
| 5 | quadlet unit validity | `<host>/.../quadlet/X.container` | `Failed to restart X.service: Unit X.service not found` — quadlet refuses to GENERATE a unit without `Image=`; also check every key the unit's comments claim (e.g. `Network=host` declared, not just argued) |
| 6 | health gate in the dispatch tail | `deploy-remote.sh` `wait_for_health` | deploy runs but hangs/fails at health |
## Pre-flight commands
```bash
# gates 1-3 in one shot (replace TARGET/SVC):
grep -c "$TARGET:$SVC" scripts/deploy.sh scripts/deploy-remote.sh # both allow-lists > 0
sed -n '/^deploy_service()/,/^}/p' scripts/deploy-remote.sh | grep "$SVC)" # dispatch case
python3 scripts/deploy-plan.py --target "$TARGET" --paths <the-service-paths> # gate 4
grep '^Image=' <host>/<svc>/quadlet/$SVC.container # gate 5 minimum
```
## Test seams that pin the class (exist post-2026-10-21)
- `tests/deployment_flow.bats` "the client and remote allowlists agree per target" — drift only; blind to a service missing from BOTH.
- `tests/deployment_flow.bats` "every gated <target> service is dispatched" — gate 3 (red-proof: plant a gated, undispatched name in a sandbox copy).
- `tests/observability_quadlet.bats` "every observability unit declares an Image" — gate 5.
## Deploy-execution traps (same tranche)
- `deploy.sh` MUST run from a checkout literally on `main` (branch-name gate) with a clean tree. A detached worktree pointed at the `main` ref fails (branch gate / dirty index). The primary checkout `~/Projects/serverAppConfig` is the deploy host; switching it to `main` needs explicit user OK when workspace policy blocks checkout.
- After merging, delete nothing by `git add -A` blind: a stray nested dir (e.g. a failed `git worktree add` stub) lands as an embedded-repo gitlink in the commit; `git rm --cached <dir> && git commit --amend` to excise, move debris to `~/.AGENTTRASH/`.
## Gitea merge invocation (consumed probe turns twice)
`POST /api/v1/repos/<o>/<r>/pulls/<n>/merge` with **JSON** body `{"Do":"merge"}` (capital D) + `Content-Type: application/json`. Form-encoded `Do=merge` 422s `[Do]: Required` on this Gitea despite older lore. Credential: `~/.config/secrets/gitea` holds `http://user:PW@host%3aport` (percent-encoded colon) — use **Basic** auth with `user:PW`; `Authorization: token` 401s. Never echo the password: build the header inside python from the file.