Stabilize Leo evidence sync without gateway restart
This commit is contained in:
parent
833e27b5c4
commit
1e9de6d864
6 changed files with 99 additions and 5 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
20
deploy/leoclean-gateway-restart-required.sh
Normal file
20
deploy/leoclean-gateway-restart-required.sh
Normal 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"
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue