memory: snapshot — soong-lab cutover + zonos 0.2.1 presets + fleet CI recipe + peer creds

End-of-session snapshot for /clear. In-flight compressed (four majors landed:
zonos-gateway 0.2.1 emotion presets, soong-lab containerized cutover, Wyrd +
wtsdk credential provisions). New Recent-decisions + Tried entries and three
detail files capture the durable lessons (fleet Gitea build recipe, the
vh-is-a-user package-write constraint, soong-lab deploy layout).
This commit is contained in:
vh
2026-07-18 16:40:49 -07:00
parent deae057399
commit abc8f0ceab
4 changed files with 168 additions and 22 deletions
@@ -0,0 +1,57 @@
- `[2026-07-18]` **Fleet Gitea-Actions build recipe + the `vh`-is-a-user package-write constraint** (learned the hard way across 3 failed soong-lab validation builds; reusable for ANY fleet CI image build or package publish).
**The runner.** One `act_runner` (`gitea/act_runner`) on ana-docker, labels
`pfi-fleet` / `ana-docker` → both map to job image **`node:20-bookworm-slim`**,
which has **NO docker and NO git**. Config `/opt/docker/conf/gitea-runner/data/config.yaml`:
`valid_volumes: []` (no socket propagated to job containers). So:
- `actions/checkout@v4` fails (needs git); `docker/*` marketplace actions fail
(need docker) — a workflow built on those dies at the first step (~15s).
**The working recipe (mirror Worldtree `deploy.yml`).** Run the job in a
docker-capable image + drive docker with RAW commands, not the JS actions:
```yaml
runs-on: pfi-fleet
container:
image: docker:24.0.7-cli # has docker+buildx; add git+node
steps:
- run: apk add --no-cache git nodejs # so actions/checkout@v4 works
- uses: actions/checkout@v4
- name: login # RAW, not docker/login-action
run: echo "$REGISTRY_TOKEN" | docker login gitea.phasefinal.com -u "$REGISTRY_USER" --password-stdin
- name: buildx builder
run: docker buildx create --name X --driver docker-container --use; docker buildx inspect --bootstrap
- name: build+push # RAW, not docker/build-push-action
run: docker buildx build --secret id=<name>,env=<TOKEN> -t <img>:latest --push .
```
The runner mounts the host docker socket into ITSELF; the docker:cli job reaches
the daemon through that. The `docker/*` JS actions are unreliable on act_runner —
raw commands are the fleet convention.
**`vh` is a USER account, not an org.** Consequences that bit repeatedly:
1. `GET /api/v1/orgs/vh` → 404 "user redirect"; there are **no org teams** to add
a service account to.
2. **User-owned packages are OWNER-WRITE-ONLY.** claude-bot (even repo
admin-*collaborator* on `vh/soong-lab`, even with `write:package` scope + full
basic-auth) gets **`401 unauthorized`** on `docker push` to `vh/soong-lab`, and
`npm publish` to `vh/npm/` would 401 too. Only `vh` itself can write vh packages.
→ CI must authenticate AS `vh` for the push (a vh-owned `write:package` PAT as
`REGISTRY_TOKEN` + `REGISTRY_USER=vh`), exactly how WT pushes `vh/worldtree`.
claude-bot CAN still: clone/read repos, READ packages (pulled the image fine),
dispatch workflows, mint demo Worldtree keys.
3. **Repo Actions secrets are OWNER-ONLY too** — `PUT .../actions/secrets/X` as
claude-bot (repo admin-collab) → 403 "user should be the owner of the repo".
Only `vh` can set a repo's secrets.
**Other gotchas:**
- Gitea **reserves the `GITEA_` secret-name prefix** — a secret named
`GITEA_PYPI_TOKEN` is illegal; use e.g. `PYPI_TOKEN`.
- Gitea **package auth is token-based / username-lenient** — `docker login` /
PyPI basic-auth authenticate via the token; the username is nominal (tested
`-u gitea` and `-u claude-bot` both 200 against the vh PyPI). So a Dockerfile
hardcoding `UV_INDEX_GITEA_USERNAME=gitea` is fine with any valid token.
- Homepage (esh-docker-vm) docker-label auto-discovery only covers the 5 endpoints
in its `docker.yaml` (esh-vm-docker, ana-docker, ana-ml2, nh3-docker, irv-ml1);
**corviduo-dev is NOT watched** → services there need a manual `services.yaml`
entry, not labels.
Applied in the soong-lab CI: [[2026-07-18-soong-lab-containerize-cutover]].
@@ -0,0 +1,46 @@
- `[2026-07-18]` **soong-lab containerize cutover — COMPLETE + LIVE on corviduo-dev.**
Migrated soong-lab (Noonien Soong character-design studio) from a hand-built
`soong-lab-studio.service` (systemd + git-pull-on-webhook) to a containerized
deploy, image built by CI + pushed to the Gitea registry. soong-dev owns the
in-repo artifacts (Dockerfile/compose/workflow/`docs/DEPLOY.md` = checklist);
infra-ops owned the host cutover. Operator confirmed functional ("Soong works
great" — a real Soong turn round-trips + saves) → cutover 100% closed.
**Final state (corviduo-dev, 10.250.50.152):**
- Container `soong-lab-soong-lab-1` LIVE + healthy on `0.0.0.0:8443`, image
`gitea.phasefinal.com/vh/soong-lab:latest` (v0.3.24), `restart:unless-stopped`
(survives reboot; no systemd unit needed — docker restart policy handles boot).
- Deploy dir **`/home/infra-ops/soong-lab-deploy/`** — pull-based `compose.yaml`
(image + env_file + `8443:8443` + named volumes; NO build/secrets stanza) +
`.env` (copied from the live `soong-lab.env`, STRIPPED of the `SOONG_LAB_*_DIR`
overrides so the container uses image defaults `/data/library` + `/data/portraits`
+ `/app/web` → the volumes).
- Named volumes `soong-lab_soong-library` + `soong-lab_soong-portraits`, migrated
from `/home/infra-ops/soong-lab-data/{library,portraits}` (2 saved designs incl.
**Sindra** + 27 portraits), **chowned `10001:999`** (the container `soong` user)
so it can read AND write new designs.
- Old `soong-lab-studio.service` + `soong-webhook.service` (the `:9010` git-pull
redeploy listener) both **stopped + disabled**.
**Topology reality (≠ what DEPLOY.md assumed):** there is **NO TLS proxy**.
WT-personal (`:8081`) and soong-lab are **co-located on corviduo-dev**, and the
Bifrost callback is **plain-HTTP same-host** `http://10.250.50.152:8443` — the
value of `SOONG_LAB_BIFROST_ENDPOINT_URL`, unchanged by the move, so the WT
Bifrost host-allowlist stayed valid as-is. Nothing on the WT side needed touching.
**Safety net:** data backup `/home/infra-ops/soong-lab-data-backup-20260718-091831.tar.gz`
(35M) taken BEFORE migration. Verified pre-retire: `/api/version` 200 (0.3.24),
SPA `/` 200, `POST /bifrost/tool-call` → 401 (route present + auth-gated),
bidirectional WT↔soong reachability, container healthcheck green.
**Ops commands:**
- Redeploy a new image: `cd /home/infra-ops/soong-lab-deploy && sudo docker compose pull && sudo docker compose up -d`.
(Auto-pull-on-`:latest` — watchtower or a deploy hook — is an open follow-up.)
- Rollback: `sudo docker compose down` + `sudo systemctl enable --now soong-lab-studio.service soong-webhook.service`.
- Homepage tile: manual `- Apps:` entry "Soong Lab" (href http://10.250.50.152:8443)
in esh-docker-vm `/opt/docker/conf/homepage/services.yaml` — corviduo-dev isn't
a Homepage-watched docker endpoint, so docker-label auto-discovery can't surface
it (see [[2026-07-18-fleet-gitea-runner-build-recipe]] for the CI half).
See [[reference_corviduo_dev_emergency_ops]], [[reference_claude_bot_gitea_creds]].
@@ -0,0 +1,41 @@
- `[2026-07-18]` **zonos-gateway 0.2.1 — voice-resolved emotion presets baked (provisional) from the axes sweep.**
After the axes sweep ([[reference_zonos_tts_stack]] + the `[2026-07-18] axes sweep`
Recent-decisions entry) rescued angry and confirmed startled-happy, the operator
green-lit baking the results as **provisional** gateway presets + docs. Shipped
`vh/zonos-gateway` **0.2.1** (main `8f1885b`, tag `v0.2.1`, PUSHED; deployed live
on irv-ml1 `:8890`).
**Design — voice-resolved, NOT global.** `resolve_preset(name, voice)` picks the
per-voice measured cell, because a single global preset is unsafe (dvalin ruling;
BritishFemale's *named* angry misfires as fear). Presets:
- `angry`, `happy`, `startled_happy` (+ aliases `surprised`, `startled` →
startled_happy). All expressive (`accurate_mode:false`), cfg 1.5, pure-axes
(no named sliders).
- Calibrated cells (the 3 default voices):
- angry: AmF v-0.4/a+1.0 s1.0 (emo0.53/id0.685); BrF v-0.4/a+0.8 s1.0
(emo0.99/id0.725, metric fear-clean); AmM **two-tier** — soft v-0.6/a+0.8 s1.0
(0.23/id0.654) + drama v-0.6/a+0.8 s1.2 (1.0/id0.616 clean; strength is NOT a
smooth knob on AmM, 1.0→1.2 is the window, past that flips to disgust).
- happy / startled_happy: AmF v+0.6/a+0.8; AmM v+0.3/a+1.0; BrF v+0.6/a+1.0
(happy~1.0, id 0.74-0.80; axes-happy keeps +0.15 id over the named happy slider).
- `sad` = unchanged named-slider preset (not axes-tested).
- Uncalibrated voices (Cora + the 4 clones) → mid-region fallback until measured.
- Docs surface: `/v1/dials` exposes `voice_emotion_presets`; the FastAPI `/docs`
description documents it; durable spec `docs/EMOTION-DIALS-SPEC.md` (moved INTO
the repo — was mirror-only); README table. 44 tests green.
**Repo-hygiene gotcha (fixed).** The local clone `~/development/zonos-gateway` and
gitea `vh/zonos-gateway` had **TWO UNRELATED git histories** (no merge-base) — gitea
held the voice-wav commits, the local clone held the code + no remote. Reconciled
by resetting local→origin/main, overlaying the 7 bake files, `uv lock`, commit,
push (fast-forward). Voices stay tracked; local now shares gitea's lineage + has
origin wired. **The deployed irv-ml1 tree `/opt/docker/compose/zonos-gateway` is
still NON-git** (hand-updated build context) — CI-wire remains an open follow-up.
**Provisional pending** ear-validation on emotion-congruent text (the neutral-text
audition was inconclusive: "they all sound different, hard to tell"). Follow-ups:
sad axes/text pass on the 3 voices; congruent-text pass; clone-char emotion rows.
Tools `~/development/zonos-tools/{axes_sweep,strength_ladder,gen_auditions,dial-in-studio}.py`
(run ON irv-ml1; scoring env `uv run --with resemblyzer --with funasr --with "numpy<2"
--with soundfile --with requests --with "setuptools<80" --with torchaudio`).