From 251c5a8687b3e850a754a7f9580c311b4d5375be Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Sat, 17 Oct 2026 11:01:56 -0700 Subject: [PATCH] feat(svos): v0.0.15 live on nh3-svos (short spoken summons, voice 0.0.2); utterance_seconds 60, Matrix request_timeout 30 --- .../nh3-svos-57d90d6-v0.0.15.yaml | 80 +++++++++++++++++++ servers/nh3-svos/README.md | 13 +++ 2 files changed, 93 insertions(+) create mode 100644 playbooks/svos-releases/nh3-svos-57d90d6-v0.0.15.yaml diff --git a/playbooks/svos-releases/nh3-svos-57d90d6-v0.0.15.yaml b/playbooks/svos-releases/nh3-svos-57d90d6-v0.0.15.yaml new file mode 100644 index 0000000..a6cd463 --- /dev/null +++ b/playbooks/svos-releases/nh3-svos-57d90d6-v0.0.15.yaml @@ -0,0 +1,80 @@ +# nh3-svos -> SVOS v0.0.15 (57d90d6): kernel 0.0.15 (the short spoken summons: no card ids, no reference; WAV cap 12 MiB) +# and app-voice 0.0.2 (utterance_seconds up to 120). No migration (schema 14), no manifest change, no card. Config: +# voice.toml utterance_seconds 30 -> 60 (needs voice 0.0.2), and [notifications.matrix] request_timeout_seconds 60 +# (this morning's stopgap) -> 30. svos-dev's ask on Prime's wording ruling (thread 01M5558E19Q3ZK4RRZXQG6A2Y9, 1059). +steps: + - name: Private staging dir + shell: install -d -m 0700 /tmp/svos-stage-57d90d6 + - {name: stage release, upload: {src: /tmp/claude-1000/-home-lkraven-development-eshpfi-management/a0a50e2d-b274-4f8c-b8e6-0606c48d35b5/scratchpad/svos-57d90d6/svos-release-57d90d6.tar.gz, dest: /tmp/svos-stage-57d90d6/release.tar.gz, mode: "0644"}} + - {name: stage uv, upload: {src: /home/lkraven/bin/uv, dest: /tmp/svos-stage-57d90d6/uv, mode: "0755"}} + - {name: stage installer, upload: {src: /tmp/claude-1000/-home-lkraven-development-eshpfi-management/a0a50e2d-b274-4f8c-b8e6-0606c48d35b5/scratchpad/svos-57d90d6/install-release.py, dest: /tmp/svos-stage-57d90d6/install-release.py, mode: "0644"}} + - name: Verify staged hashes + shell: cd /tmp/svos-stage-57d90d6 && printf '%s %s\n' ffee056d65ec4df4c916611c678b9268b0c8bbf3339e865cfd8a3ca2075448c3 release.tar.gz 83aa128da93e168283ed32cf75cd819fe17b135b3fc1f7ccb3c912eec198d9fb uv | sha256sum -c - + changed_when: "false" + - name: Install release 57d90d6 beside the live one (current NOT switched) + sudo: true + shell: python3 /tmp/svos-stage-57d90d6/install-release.py + creates: /opt/svos/releases/57d90d6/apps-env/pyvenv.cfg + - name: New kernel accepts the live configuration as it is + sudo: true + shell: runuser -u svc-svos -- /opt/svos/releases/57d90d6/kernel-env/bin/svos-kernel check-config --data-root /var/lib/svos + - name: Stop the local apps then the kernel + sudo: true + shell: systemctl stop svos-voice svos-knowledge svos-interface svos-kernel; systemctl is-active svos-kernel svos-interface svos-knowledge svos-voice | tr '\n' ' '; echo + - name: Offline backup to /var/lib/svos/backup/pre-57d90d6 (database + both configs) + sudo: true + shell: | + set -e + d=/var/lib/svos/backup/pre-57d90d6 + runuser -u svc-svos -- install -d -m 0700 $d + runuser -u svc-svos -- python3 -c " + import sqlite3 + s = sqlite3.connect('/var/lib/svos/state/svos.db'); t = sqlite3.connect('$d/svos.db'); s.backup(t); t.close(); s.close() + c = sqlite3.connect('file:$d/svos.db?mode=ro', uri=True); print('integrity', c.execute('pragma integrity_check').fetchone()[0], 'user_version', c.execute('pragma user_version').fetchone()[0])" + runuser -u svc-svos -- cp -p /var/lib/svos/config/instance.toml /var/lib/svos/config/instance.toml.pre-57d90d6 + runuser -u svc-svos -- cp -p /var/lib/svos/config/apps/voice.toml /var/lib/svos/config/apps/voice.toml.pre-57d90d6 + - name: Switch current to 57d90d6 + sudo: true + shell: ln -sfn /opt/svos/releases/57d90d6 /opt/svos/current.new && chgrp -h svc-svos /opt/svos/current.new && mv -T /opt/svos/current.new /opt/svos/current && readlink /opt/svos/current + - name: Config - voice utterance_seconds 60, Matrix request_timeout_seconds 30; the new kernel must accept it (else restore all + old release) + sudo: true + shell: | + runuser -u svc-svos -- python3 - <<'PY' + import os + def write(p, s): + t = p + ".tmp"; fd = os.open(t, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w") as f: f.write(s) + os.replace(t, p) + p = "/var/lib/svos/config/apps/voice.toml"; s = open(p).read() + assert s.count("utterance_seconds = 30\n") == 1 + write(p, s.replace("utterance_seconds = 30\n", "utterance_seconds = 60 # 2026-10-17 v0.0.15 (voice 0.0.2 admits 0.1..120); 6 MiB output holds 65 s at 48 kHz\n")) + p = "/var/lib/svos/config/instance.toml"; s = open(p).read() + old = "request_timeout_seconds = 60 # 2026-10-17 stopgap (svos-dev): summons synthesis needs > 15 s; range 15..60\n" + assert s.count(old) == 1 + write(p, s.replace(old, "request_timeout_seconds = 30 # 2026-10-17 v0.0.15: the short spoken summons is 5-9 s of audio; headroom for a slow gateway\n")) + print("edited") + PY + if runuser -u svc-svos -- /opt/svos/releases/57d90d6/kernel-env/bin/svos-kernel check-config --data-root /var/lib/svos; then exit 0; fi + runuser -u svc-svos -- cp -p /var/lib/svos/config/instance.toml.pre-57d90d6 /var/lib/svos/config/instance.toml + runuser -u svc-svos -- cp -p /var/lib/svos/config/apps/voice.toml.pre-57d90d6 /var/lib/svos/config/apps/voice.toml + ln -sfn /opt/svos/releases/3726d73 /opt/svos/current.new && chgrp -h svc-svos /opt/svos/current.new && mv -T /opt/svos/current.new /opt/svos/current + systemctl start svos-kernel.service svos-interface svos-knowledge svos-voice; echo "RESTORED configs + 3726d73"; exit 1 + - name: Start the kernel (READY-gated, no migration) then the local apps + sudo: true + shell: | + t0=$(date +%s.%N); systemctl start svos-kernel.service; rc=$?; t1=$(date +%s.%N) + echo "kernel start rc=$rc after $(python3 -c "print(round($t1-$t0,2))") s" + systemctl start svos-interface svos-knowledge svos-voice + for i in $(seq 1 60); do h=$(curl -s http://127.0.0.1:8400/health); case "$h" in *'"down":0'*) case "$h" in *'"unknown":0'*) break;; esac;; esac; sleep 1; done + echo "$h"; systemctl is-active svos-kernel svos-interface svos-knowledge svos-voice | tr '\n' ' '; echo + - name: Regenerate the front door inventory + sudo: true + shell: /usr/local/sbin/svos-front-inventory 2>&1 | grep -E 'GENERATED|updated|root \*|path /assets' | head -6 +verify: + - name: Health is schema 14, status ok, nothing down + shell: h=$(curl -s http://127.0.0.1:8400/health); echo "$h"; echo "$h" | grep -q '"schema_version":14' && echo "$h" | grep -q '"status":"ok"' && echo "$h" | grep -q '"down":0' + changed_when: "false" + - name: Front door inventory current (regenerated after the switch; the header names the release dir even when the web build is byte-identical) + sudo: true + shell: /usr/local/sbin/svos-front-inventory --check + changed_when: "false" diff --git a/servers/nh3-svos/README.md b/servers/nh3-svos/README.md index 3941b45..6ec857f 100644 --- a/servers/nh3-svos/README.md +++ b/servers/nh3-svos/README.md @@ -23,6 +23,19 @@ configuration, enrollment and acceptance. | App-consistent hook | svos-dev's `/etc/qemu/fsfreeze-hook.d/50-svos` stops the SVOS services on freeze and restarts the ones that were running on thaw. ⚠ **Debian's qemu-ga does NOT run fsfreeze hooks unless configured** (no `-F` in the unit, no `qemu-ga.conf` by default). Here `/etc/qemu/qemu-ga.conf` points it at **`/usr/local/sbin/fsfreeze-dispatch`** (`configs/qemu-ga/fsfreeze-dispatch`), which unlike the stock `/etc/qemu/fsfreeze-hook` propagates failure: if a freeze hook fails, it thaws that hook and the completed ones and exits 1, so qemu-ga refuses the freeze. **Live drill 2026-10-05 1952–1953 (services running):** quiesced backup `vm/111/2026-10-06T02:52:11Z` → kernel+interface down ~1–2 s, back by themselves, health ok. Refused freeze (temporary `60-drill-refuse` after 50-svos, since removed) `vm/111/2026-10-06T02:53:03Z` → the dispatcher unwound and restarted the services, and then **⚠ vzdump did NOT abort: it logged `ERROR: unable to freeze guest fs`, backed up UNFROZEN (crash-consistent, app live), and reported `Backup job finished successfully`, rc 0.** Both images mapped from PBS and mounted read-only (`ro,noload`) on nh3-pve-2, never booted: `svos.db` integrity ok (each image still carries its ~0.9 MB WAL: keep `-wal` with the db when restoring), interface `pins.sqlite3` integrity ok, config/key/config-generation hashes equal live, table counts equal live except `events`. **Monitoring:** `scripts/check-backup-freshness.sh` now reads the LOG of every OK vzdump task and reports ERROR lines; a freeze/thaw error is an APPLICATION-CONSISTENCY FAILURE, verdict **DEGRADED, exit 4** (alerted by the daily wrapper to infra-hermes). nh3-pve-2 was not in the check's node lists at all before 2026-10-05; it is now. Drill B is acknowledged by UPID in the script | | Monitoring | Beszel native agent 0.18.7 (`playbooks/beszel-native.yaml`, `configs/beszel-agent/hosts/nh3-svos.env`), hub system `t6y646ifa3x7xxv`, alerts CPU>95%/15m, Disk>85%/5m, Memory>90%/10m, offline/2m. Up 2026-10-05 | +## Release 57d90d6 / v0.0.15, the short spoken summons (2026-10-17 1100–1102; Prime's wording ruling via svos-dev, thread `01M5558E19Q3ZK4RRZXQG6A2Y9`) + +- **Kit:** kernel 0.0.15 and app-voice 0.0.2 wheels from a clean archive of the tag. Everything else, the web build included, was carried byte-identical + from 3726d73 (the UI bar still reads 3726d73; its sources are unchanged). Kit sha256 `ffee056d…`. Playbook: `nh3-svos-57d90d6-v0.0.15.yaml`. +- **Spoken form:** a card summons now speaks "Pending card from : . Created . Approve or countermand from your desk." + with no ids and no reference. The Matrix text is unchanged. The kernel's WAV cap is 12 MiB. +- **Config:** `voice.toml` `utterance_seconds` 30 → 60 (voice 0.0.2 admits 0.1..120). `[notifications.matrix] request_timeout_seconds` + 60 (this morning's stopgap) → 30. Config gen `40f469b75b5d0782`. No migration (schema 14) and no manifest change, so no card. + Kernel READY in 0.76 s; all 7 apps up by 1101:21. +- **Backup / rollback:** `backup/pre-57d90d6/svos.db` (integrity ok, v14), plus `instance.toml.pre-57d90d6` and `apps/voice.toml.pre-57d90d6`. + To roll back: stop the apps and the kernel, restore both configs, then `current → releases/3726d73`, then start. +- ⚠ The front-door inventory header names the release dir, so regenerate it after every switch, even when the web build is identical. + ## Release 3726d73 / v0.0.14, the task-seat success gate (2026-10-17 0908–0910; Prime's in-session go, svos-dev thread `01M5558E19Q3ZK4RRZXQG6A2Y9`) - **Kit:** kernel 0.0.14, connector base 0.0.10, app-interface 0.0.6, app-terminal 0.0.5 wheels and web build UI 0.0.19, all from a clean archive of