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
# Restart services only if Python files changed
# Restart services only when changed files affect a running process.
RESTART=""
add_restart() {
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
add_restart teleo-diagnostics
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
fi
else
@ -216,7 +217,7 @@ if [ -n "$RESTART" ]; then
exit 1
fi
else
log "No Python changes — services not restarted"
log "No runtime changes - services not restarted"
fi
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`,
`claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and
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`,
`new or updated rows`, `public.*`, and `postflight proof` where relevant. End
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`,
`claim_evidence`, and `kb_proposals` tables. Short eight-character IDs and
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`,
`new or updated rows`, `public.*`, and `postflight proof` where relevant. End
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 "full UUID plus observed `status` and `applied_at`" in squashed
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 "not canonical Postgres" 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 "full UUID plus observed `status` and `applied_at`" in squashed
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 "not canonical Postgres" in text
assert "not the source of truth" in text

View file

@ -1,3 +1,4 @@
import subprocess
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
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()
manual_deploy = (REPO_ROOT / "deploy" / "deploy.sh").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 'hermes-agent/leoclean-bin/' 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 'sudo systemctl restart "$svc"' 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
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():
installer = (REPO_ROOT / "deploy" / "install-auto-deploy-sudoers.sh").read_text()