feat(svos): v0.0.14 (3726d73) release playbooks — staged on nh3-svos and built on nh3-dev; cutover in three phases held for Prime's card window

This commit is contained in:
vh
2026-10-17 07:50:44 -07:00
parent bc91b40ac5
commit b72783cb3c
4 changed files with 143 additions and 0 deletions
@@ -0,0 +1,43 @@
# nh3-dev PHASE B of the v0.0.14 rollout: connector base 0.0.10 + app-terminal 0.0.5 (release 3726d73 already built beside
# the live one in /opt/svos/releases/3726d73). Adds [seat_host].scan_patterns (hermes's threat-pattern table) and restarts
# ONLY svos-terminal; postoffice, miranda and agent keep their running code until their next restart. Terminal's manifest
# adds the `scan` read verb: registration raises ONE card, and the app refuses wrong-revision until Prime approves it.
# Run as: scripts/elway infra-ops@10.100.10.50 (the apps are lkraven USER units, driven with runuser + XDG_RUNTIME_DIR).
steps:
- name: Release 3726d73 is built and pip-clean
shell: test -f /opt/svos/releases/3726d73/release.json && /home/lkraven/bin/uv pip check --python /opt/svos/releases/3726d73/apps-env/bin/python | tail -1
changed_when: "false"
- name: Back up terminal.toml, then add scan_patterns under [seat_host]
sudo: true
shell: |
set -e
p=/var/lib/svos/config/apps/terminal.toml
runuser -u lkraven -- cp -p $p $p.pre-3726d73
runuser -u lkraven -- python3 - <<'PY'
import os
p = "/var/lib/svos/config/apps/terminal.toml"; s = open(p).read()
anchor = 'tasks_root = "/var/lib/svos/apps/terminal/tasks" # U15 (v0.0.11): = the kernel\'s [tasks].scratch_root\n'
assert s.count(anchor) == 1 and "scan_patterns" not in s
s = s.replace(anchor, anchor + 'scan_patterns = "/home/lkraven/.hermes/hermes-agent/tools/threat_patterns.py" # U15 v0.7 (v0.0.14): hermes\'s own threat table; /health then says scan: on\n')
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); print("edited")
PY
- name: Switch current to 3726d73 (as lkraven, who owns /opt/svos on nh3-dev)
sudo: true
shell: runuser -u lkraven -- sh -c 'ln -sfn /opt/svos/releases/3726d73 /opt/svos/current.new && mv -T /opt/svos/current.new /opt/svos/current && readlink /opt/svos/current'
- name: Restart ONLY svos-terminal
sudo: true
shell: |
runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 systemctl --user restart svos-terminal
sleep 6; runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 systemctl --user is-active svos-terminal
pid=$(runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 systemctl --user show svos-terminal -p MainPID --value); grep -oE '/opt/svos/releases/[0-9a-f]+' /proc/$pid/maps | sort -u
runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 journalctl --user -u svos-terminal --since "-60s" --no-pager -o cat | grep -i -E "scan|manifest|card|held|revision|error" | tail -8 | cut -c1-200
verify:
- name: Terminal runs 3726d73 and logged its scan table (its /health needs a signed request, so read the log)
sudo: true
shell: |
pid=$(runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 systemctl --user show svos-terminal -p MainPID --value)
grep -q /opt/svos/releases/3726d73 /proc/$pid/maps && echo "terminal pid $pid on 3726d73"
runuser -u lkraven -- env XDG_RUNTIME_DIR=/run/user/1000 journalctl --user -u svos-terminal --since "-5min" --no-pager -o cat | grep -i scan | tail -3
changed_when: "false"
@@ -0,0 +1,23 @@
# nh3-svos PHASE C of the v0.0.14 rollout: start app-interface 0.0.6 (already installed in /opt/svos/current = 3726d73 by
# phase A). Its first start migrates the briefs index v1->v2 (copy kept in backup/pre-3726d73) and raises ONE card (kind/
# name on the brief verbs): the interface refuses wrong-revision until Prime approves it. Then regenerate the front door.
steps:
- name: current is 3726d73 and the kernel is schema 14
shell: test "$(readlink /opt/svos/current)" = /opt/svos/releases/3726d73 && curl -s http://127.0.0.1:8400/health | grep -q '"schema_version":14'
changed_when: "false"
- name: Start the interface
sudo: true
shell: |
systemctl start svos-interface; sleep 6; systemctl is-active svos-interface
journalctl -u svos-interface --since "-60s" --no-pager -o cat | grep -i -E "brief|migrat|v2|manifest|card|held|error" | tail -8 | cut -c1-200
- name: Regenerate the front door inventory (33a7077 becomes the previous release)
sudo: true
shell: /usr/local/sbin/svos-front-inventory 2>&1 | grep -E 'GENERATED|updated|root \*|path /assets' | head -6
verify:
- name: Inventory current
sudo: true
shell: /usr/local/sbin/svos-front-inventory --check
changed_when: "false"
- name: Kernel health (the interface reads down until its card is approved)
shell: curl -s http://127.0.0.1:8400/health
changed_when: "false"
@@ -0,0 +1,24 @@
# nh3-svos: STAGE SVOS v0.0.14 (3726d73) beside the live release. Installs into /opt/svos/releases/3726d73 and runs the
# new kernel's check-config against the live configuration. Does NOT switch current, stop or start anything.
# svos-dev's ask (thread 01M5558E19Q3ZK4RRZXQG6A2Y9), pushed on Prime's 0245 go. The cutover is nh3-svos-3726d73-v0.0.14.yaml.
steps:
- name: Private staging dir
shell: install -d -m 0700 /tmp/svos-stage-3726d73
- {name: stage release, upload: {src: /tmp/claude-1000/-home-lkraven-development-eshpfi-management/a0a50e2d-b274-4f8c-b8e6-0606c48d35b5/scratchpad/svos-3726d73/svos-release-3726d73.tar.gz, dest: /tmp/svos-stage-3726d73/release.tar.gz, mode: "0644"}}
- {name: stage uv, upload: {src: /home/lkraven/bin/uv, dest: /tmp/svos-stage-3726d73/uv, mode: "0755"}}
- {name: stage installer, upload: {src: /tmp/claude-1000/-home-lkraven-development-eshpfi-management/a0a50e2d-b274-4f8c-b8e6-0606c48d35b5/scratchpad/svos-3726d73/install-release.py, dest: /tmp/svos-stage-3726d73/install-release.py, mode: "0644"}}
- name: Verify staged hashes
shell: cd /tmp/svos-stage-3726d73 && printf '%s %s\n' 380e64f6c5fa7d7e83844483600622bcda1a6bbe820ba9cf5854d0fc0ab32715 release.tar.gz 83aa128da93e168283ed32cf75cd819fe17b135b3fc1f7ccb3c912eec198d9fb uv | sha256sum -c -
changed_when: "false"
- name: Install release 3726d73 beside the live one (current NOT switched)
sudo: true
shell: python3 /tmp/svos-stage-3726d73/install-release.py
creates: /opt/svos/releases/3726d73/apps-env/pyvenv.cfg
verify:
- name: New kernel 0.0.14 accepts the live configuration as it is
sudo: true
shell: runuser -u svc-svos -- /opt/svos/releases/3726d73/kernel-env/bin/svos-kernel check-config --data-root /var/lib/svos
changed_when: "false"
- name: current still points at the live release
shell: readlink /opt/svos/current
changed_when: "false"
@@ -0,0 +1,53 @@
# nh3-svos -> SVOS v0.0.14 (3726d73): kernel 0.0.14 (migration 0014, schema 14), app-interface 0.0.6 (briefs index v1->v2 at
# its first start), connector base 0.0.10, UI 0.0.19. svos-dev's ask (thread 01M5558E19Q3ZK4RRZXQG6A2Y9), pushed on Prime's
# 0245 go. PRE-REQ: nh3-svos-3726d73-v0.0.14-stage.yaml (installs the release beside the live one).
# PHASE A of three, in svos-dev's order: A = this (kernel, knowledge, voice; the interface is LEFT STOPPED),
# B = nh3-dev-svos-3726d73-v0.0.14.yaml (connector base + app-terminal; raises ONE card),
# C = nh3-svos-3726d73-v0.0.14-interface.yaml (app-interface + the web build; raises ONE card).
# Run A..C only while Prime can approve both cards within 15 minutes: a held app refuses wrong-revision (reads DOWN).
steps:
- name: The release is staged and accepts the live config
sudo: true
shell: test -f /opt/svos/releases/3726d73/apps-env/pyvenv.cfg && runuser -u svc-svos -- /opt/svos/releases/3726d73/kernel-env/bin/svos-kernel check-config --data-root /var/lib/svos
changed_when: "false"
- 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-3726d73 (schema 13 database, the v1 briefs index, config)
sudo: true
shell: |
set -e
d=/var/lib/svos/backup/pre-3726d73
runuser -u svc-svos -- install -d -m 0700 $d
runuser -u svc-svos -- python3 -c "
import sqlite3
for src, dst in (('/var/lib/svos/state/svos.db', '$d/svos.db'), ('/var/lib/svos/state/apps/interface/briefs.sqlite3', '$d/briefs.sqlite3')):
s = sqlite3.connect(src); t = sqlite3.connect(dst); s.backup(t); t.close(); s.close()
c = sqlite3.connect('file:' + dst + '?mode=ro', uri=True)
print(dst, 'integrity', c.execute('pragma integrity_check').fetchone()[0], 'user_version', c.execute('pragma user_version').fetchone()[0])"
runuser -u svc-svos -- tar -C /var/lib/svos -cf $d/config-keys-apps.tar config state/keys state/apps state/config-generations apps
runuser -u svc-svos -- cp -p /var/lib/svos/config/instance.toml /var/lib/svos/config/instance.toml.pre-3726d73
ls -la $d | tail -4
- name: Switch current to 3726d73
sudo: true
shell: 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 && readlink /opt/svos/current
- name: Start the kernel (READY-gated; migration 0014) then knowledge and voice (the interface waits for phase C)
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-knowledge svos-voice
sleep 5; curl -s http://127.0.0.1:8400/health; echo
systemctl is-active svos-kernel svos-interface svos-knowledge svos-voice | tr '\n' ' '; echo
journalctl -u svos-kernel --since "-90s" --no-pager -o cat | grep -i -E "migrat|0014|schema|error|refus" | tail -6 | cut -c1-200
test $rc -eq 0
verify:
- name: Health is schema 14, status ok
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"'
changed_when: "false"
- name: GET /seats profiles.tasks publishes brief_max_chars and ceilings.check_seconds (read with the local admin path if open, else skip)
shell: curl -s http://127.0.0.1:8400/seats | python3 -c "import sys,json; d=json.load(sys.stdin); t=d.get('profiles',{}).get('tasks',{}); print('brief_max_chars', t.get('brief_max_chars'), 'check_seconds', (t.get('ceilings') or {}).get('check_seconds'))" 2>&1 || true
changed_when: "false"
- name: App listener still refuses an uncredentialed kernel-key (401 from the kernel)
shell: test "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8410/auth/kernel-key)" = 401
changed_when: "false"