Merge pull request #82 from living-ip/codex/vps-cory-evidence-template-20260712
Some checks are pending
CI / lint-and-test (push) Waiting to run

Stabilize Leo evidence sync without gateway restart
This commit is contained in:
twentyOne2x 2026-07-12 10:45:21 +02:00 committed by GitHub
commit 68845111ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 99 additions and 5 deletions

View file

@ -131,7 +131,7 @@ if [ "$OLD_SHA" = "none" ] || git diff --name-only "$OLD_SHA" "$NEW_SHA" -- syst
fi fi
fi fi
# Restart services only if Python files changed # Restart services only when changed files affect a running process.
RESTART="" RESTART=""
add_restart() { add_restart() {
case " $RESTART " in case " $RESTART " in
@ -164,7 +164,8 @@ if [ "$OLD_SHA" != "none" ]; then
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- diagnostics/ 2>/dev/null | grep -q '\.py$'; then if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- diagnostics/ 2>/dev/null | grep -q '\.py$'; then
add_restart teleo-diagnostics add_restart teleo-diagnostics
fi fi
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/ 2>/dev/null | grep -q .; then if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/ 2>/dev/null \
| bash deploy/leoclean-gateway-restart-required.sh; then
add_restart_if_unit_active leoclean-gateway add_restart_if_unit_active leoclean-gateway
fi fi
else else
@ -216,7 +217,7 @@ if [ -n "$RESTART" ]; then
exit 1 exit 1
fi fi
else else
log "No Python changes — services not restarted" log "No runtime changes - services not restarted"
fi fi
echo "$NEW_SHA" > "$STAMP_FILE" echo "$NEW_SHA" > "$STAMP_FILE"

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
# Read changed repo paths from stdin. Markdown skills are loaded from the synced
# profile per turn; runtime code changes still require a gateway restart.
restart_required=1
while IFS= read -r path || [ -n "$path" ]; do
case "$path" in
hermes-agent/leoclean-bin/*)
restart_required=0
;;
hermes-agent/leoclean-skills/vps/*.md|hermes-agent/leoclean-skills/vps/*/*.md)
;;
hermes-agent/leoclean-skills/vps/*)
restart_required=0
;;
esac
done
exit "$restart_required"

View file

@ -152,6 +152,22 @@ and copy either (a) a full UUID plus observed `status` and `applied_at`, or (b)
exact observed counts for the relevant `claims`, `sources`, `claim_edges`, exact observed counts for the relevant `claims`, `sources`, `claim_edges`,
`claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and `claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and
phrases such as `current readback` are not structured proof by themselves. phrases such as `current readback` are not structured proof by themselves.
Copy exactly one of these formats, replacing every all-caps token with a value
from the current tool call:
```text
DB readback: proposal: `PROPOSAL_UUID_36_CHARS`; status: `OBSERVED_STATUS`; applied_at: `OBSERVED_TIMESTAMP_OR_NONE`.
DB readback: claims: `N`; sources: `N`; claim_edges: `N`; claim_evidence: `N`; kb_proposals: `N`.
```
Never shorten a UUID: it must contain all 36 characters and four hyphens. Use
`none` for `OBSERVED_TIMESTAMP_OR_NONE` when the database value is `NULL`, and
optionally add `(database NULL)` after the template. Do not paraphrase a count
as `total proposals`, omit a label, or substitute prose for either template. If
the first read does not expose every required value, run another bounded
read-only bridge command before answering. Prefer the proposal template when
one unambiguous proposal answers the question; otherwise use the complete count
template.
Also use row-level proof vocabulary such as `row-link audit`, `row IDs`, Also use row-level proof vocabulary such as `row-link audit`, `row IDs`,
`new or updated rows`, `public.*`, and `postflight proof` where relevant. End `new or updated rows`, `public.*`, and `postflight proof` where relevant. End
with exactly one final line beginning with exactly one final line beginning

View file

@ -145,6 +145,22 @@ and copy either (a) a full UUID plus observed `status` and `applied_at`, or (b)
exact observed counts for the relevant `claims`, `sources`, `claim_edges`, exact observed counts for the relevant `claims`, `sources`, `claim_edges`,
`claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and `claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and
phrases such as `current readback` are not structured proof by themselves. phrases such as `current readback` are not structured proof by themselves.
Copy exactly one of these formats, replacing every all-caps token with a value
from the current tool call:
```text
DB readback: proposal: `PROPOSAL_UUID_36_CHARS`; status: `OBSERVED_STATUS`; applied_at: `OBSERVED_TIMESTAMP_OR_NONE`.
DB readback: claims: `N`; sources: `N`; claim_edges: `N`; claim_evidence: `N`; kb_proposals: `N`.
```
Never shorten a UUID: it must contain all 36 characters and four hyphens. Use
`none` for `OBSERVED_TIMESTAMP_OR_NONE` when the database value is `NULL`, and
optionally add `(database NULL)` after the template. Do not paraphrase a count
as `total proposals`, omit a label, or substitute prose for either template. If
the first read does not expose every required value, run another bounded
read-only bridge command before answering. Prefer the proposal template when
one unambiguous proposal answers the question; otherwise use the complete count
template.
Also use row-level proof vocabulary such as `row-link audit`, `row IDs`, Also use row-level proof vocabulary such as `row-link audit`, `row IDs`,
`new or updated rows`, `public.*`, and `postflight proof` where relevant. End `new or updated rows`, `public.*`, and `postflight proof` where relevant. End
with exactly one final line beginning with exactly one final line beginning

View file

@ -32,6 +32,12 @@ def test_gcp_kb_skill_uses_cloudsql_bridge_not_vps_docker() -> None:
assert "DB readback:" in text assert "DB readback:" in text
assert "full UUID plus observed `status` and `applied_at`" in squashed assert "full UUID plus observed `status` and `applied_at`" in squashed
assert "Short eight-character IDs" in text assert "Short eight-character IDs" in text
assert "PROPOSAL_UUID_36_CHARS" in text
assert "OBSERVED_STATUS" in text
assert "OBSERVED_TIMESTAMP_OR_NONE" in text
assert "claims: `N`; sources: `N`; claim_edges: `N`; claim_evidence: `N`; kb_proposals: `N`" in text
assert "Never shorten a UUID" in text
assert "Do not paraphrase a count" in text
assert "canonical identity requires DB rows plus" in squashed assert "canonical identity requires DB rows plus" in squashed
assert "not canonical Postgres" in text assert "not canonical Postgres" in text
assert "not the source of truth" in text assert "not the source of truth" in text
@ -77,6 +83,12 @@ def test_vps_kb_skill_keeps_vps_scope_explicit() -> None:
assert "DB readback:" in text assert "DB readback:" in text
assert "full UUID plus observed `status` and `applied_at`" in squashed assert "full UUID plus observed `status` and `applied_at`" in squashed
assert "Short eight-character IDs" in text assert "Short eight-character IDs" in text
assert "PROPOSAL_UUID_36_CHARS" in text
assert "OBSERVED_STATUS" in text
assert "OBSERVED_TIMESTAMP_OR_NONE" in text
assert "claims: `N`; sources: `N`; claim_edges: `N`; claim_evidence: `N`; kb_proposals: `N`" in text
assert "Never shorten a UUID" in text
assert "Do not paraphrase a count" in text
assert "canonical identity requires DB rows plus" in squashed assert "canonical identity requires DB rows plus" in squashed
assert "not canonical Postgres" in text assert "not canonical Postgres" in text
assert "not the source of truth" in text assert "not the source of truth" in text

View file

@ -1,3 +1,4 @@
import subprocess
from pathlib import Path from pathlib import Path
@ -80,7 +81,19 @@ def test_deploy_scripts_install_systemd_units_and_enable_agent_healthcheck():
assert "teleo-agent-healthcheck.timer" in manual_deploy assert "teleo-agent-healthcheck.timer" in manual_deploy
def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_when_active(): def _leoclean_gateway_restart_required(*changed_paths: str) -> bool:
result = subprocess.run(
["bash", str(REPO_ROOT / "deploy" / "leoclean-gateway-restart-required.sh")],
input="\n".join(changed_paths),
capture_output=True,
check=False,
text=True,
)
assert result.returncode in {0, 1}, result.stderr
return result.returncode == 0
def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_changes():
auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text() auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text()
manual_deploy = (REPO_ROOT / "deploy" / "deploy.sh").read_text() manual_deploy = (REPO_ROOT / "deploy" / "deploy.sh").read_text()
sudoers = (REPO_ROOT / "deploy" / "sudoers" / "teleo-auto-deploy").read_text() sudoers = (REPO_ROOT / "deploy" / "sudoers" / "teleo-auto-deploy").read_text()
@ -89,7 +102,7 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_when_active():
assert 'LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"' in auto_deploy assert 'LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"' in auto_deploy
assert 'hermes-agent/leoclean-bin/' in auto_deploy assert 'hermes-agent/leoclean-bin/' in auto_deploy
assert 'hermes-agent/leoclean-skills/vps/' in auto_deploy assert 'hermes-agent/leoclean-skills/vps/' in auto_deploy
assert 'git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/' in auto_deploy assert 'deploy/leoclean-gateway-restart-required.sh' in auto_deploy
assert 'add_restart_if_unit_active leoclean-gateway' in auto_deploy assert 'add_restart_if_unit_active leoclean-gateway' in auto_deploy
assert 'sudo systemctl restart "$svc"' in auto_deploy assert 'sudo systemctl restart "$svc"' in auto_deploy
assert "sudo systemctl restart $RESTART" not in auto_deploy assert "sudo systemctl restart $RESTART" not in auto_deploy
@ -102,6 +115,22 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_when_active():
assert 'systemctl is-active --quiet leoclean-gateway.service' in manual_deploy assert 'systemctl is-active --quiet leoclean-gateway.service' in manual_deploy
def test_auto_deploy_hot_syncs_leoclean_markdown_without_gateway_restart():
assert not _leoclean_gateway_restart_required(
"hermes-agent/leoclean-skills/vps/teleo-kb-bridge/SKILL.md"
)
assert not _leoclean_gateway_restart_required(
"hermes-agent/leoclean-skills/vps/live-leo-telegram/SKILL.md"
)
def test_auto_deploy_restarts_gateway_for_leoclean_runtime_changes():
assert _leoclean_gateway_restart_required("hermes-agent/leoclean-bin/kb_tool.py")
assert _leoclean_gateway_restart_required(
"hermes-agent/leoclean-skills/vps/teleo-kb-bridge/runtime_helper.py"
)
def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked(): def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked():
installer = (REPO_ROOT / "deploy" / "install-auto-deploy-sudoers.sh").read_text() installer = (REPO_ROOT / "deploy" / "install-auto-deploy-sudoers.sh").read_text()