From b72783cb3c1b18fc56d4c65e45f3f3094a032229 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Sat, 17 Oct 2026 07:50:44 -0700 Subject: [PATCH] =?UTF-8?q?feat(svos):=20v0.0.14=20(3726d73)=20release=20p?= =?UTF-8?q?laybooks=20=E2=80=94=20staged=20on=20nh3-svos=20and=20built=20o?= =?UTF-8?q?n=20nh3-dev;=20cutover=20in=20three=20phases=20held=20for=20Pri?= =?UTF-8?q?me's=20card=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nh3-dev-svos-3726d73-v0.0.14.yaml | 43 +++++++++++++++ .../nh3-svos-3726d73-v0.0.14-interface.yaml | 23 ++++++++ .../nh3-svos-3726d73-v0.0.14-stage.yaml | 24 +++++++++ .../nh3-svos-3726d73-v0.0.14.yaml | 53 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 playbooks/svos-releases/nh3-dev-svos-3726d73-v0.0.14.yaml create mode 100644 playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-interface.yaml create mode 100644 playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-stage.yaml create mode 100644 playbooks/svos-releases/nh3-svos-3726d73-v0.0.14.yaml diff --git a/playbooks/svos-releases/nh3-dev-svos-3726d73-v0.0.14.yaml b/playbooks/svos-releases/nh3-dev-svos-3726d73-v0.0.14.yaml new file mode 100644 index 0000000..f3f9403 --- /dev/null +++ b/playbooks/svos-releases/nh3-dev-svos-3726d73-v0.0.14.yaml @@ -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" diff --git a/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-interface.yaml b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-interface.yaml new file mode 100644 index 0000000..6f3f1e0 --- /dev/null +++ b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-interface.yaml @@ -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" diff --git a/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-stage.yaml b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-stage.yaml new file mode 100644 index 0000000..263a8c8 --- /dev/null +++ b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14-stage.yaml @@ -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" diff --git a/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14.yaml b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14.yaml new file mode 100644 index 0000000..271645d --- /dev/null +++ b/playbooks/svos-releases/nh3-svos-3726d73-v0.0.14.yaml @@ -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"