Files
Jyotisha/tests/test_beta_safety_net.py
T
jesse-uxandGrok (agent loop) 9455fbfc4f fix(release): keep daily backups from stalling when the disk fills
Expired daily files are pruned before a new backup, a verify older than 36 hours raises an alert, and the host monitors call sudo with the production env file. The Linux 0600 check is on the staging quick list.

Co-Authored-By: Grok (agent loop)
2026-10-15 17:24:26 +08:00

1000 lines
38 KiB
Python

"""Contracts for the closed-beta safety net. Fixtures are fictional."""
from __future__ import annotations
import json
import os
import shutil
import subprocess
import sys
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
WORKFLOW = ROOT / ".gitea" / "workflows" / "backup-production-daily.yml"
RECOVERY_WORKFLOW = ROOT / ".gitea" / "workflows" / "create-production-recovery.yml"
RECOVERY_SCRIPT = ROOT / "deploy" / "run-production-recovery.sh"
PRUNE_SCRIPT = ROOT / "deploy" / "prune-production-daily-backups.sh"
HEALTH_SCRIPT = ROOT / "deploy" / "monitor" / "healthcheck-alert.sh"
DIGEST_SCRIPT = ROOT / "deploy" / "monitor" / "error-digest.sh"
FIND_SCRIPT = ROOT / "deploy" / "find-request-log.sh"
NOTIFY_SCRIPT = ROOT / "deploy" / "monitor" / "notify-feishu.sh"
SERVER_COMPOSE = ROOT / "deploy" / "docker-compose.server.yml"
POSTGRES_COMPOSE = ROOT / "deploy" / "docker-compose.postgres.yml"
def resolve_bash() -> str:
found = shutil.which("bash")
git_bash = Path(r"D:\Program Files\Git\bin\bash.exe")
if os.name == "nt" and git_bash.exists():
lowered = (found or "").lower()
if not found or "system32" in lowered or "windowsapps" in lowered:
return str(git_bash)
if found:
return found
if git_bash.exists():
return str(git_bash)
return "bash"
BASH = resolve_bash()
def bash_path(path: Path) -> str:
if os.name != "nt":
return str(path)
completed = subprocess.run(
[BASH, "-lc", 'cygpath -u "$1"', "cygpath", str(path)],
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
return completed.stdout.strip()
def run_bash(script: Path, extra: dict[str, str], args: list[str] | None = None) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env.pop("FEISHU_WEBHOOK_URL", None)
env.pop("FEISHU_SIGN_SECRET", None)
env.update(extra)
env["JYOTISHA_PYTHON"] = bash_path(Path(sys.executable))
return subprocess.run(
[BASH, bash_path(script), *(args or [])],
cwd=ROOT,
env=env,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
def write_text(path: Path, text: str) -> None:
path.write_text(text, encoding="utf-8", newline="\n")
def write_backup_record(directory: Path, verified_at: str) -> Path:
record = directory / "daily-backup-latest.env"
write_text(
record,
"BACKUP_BASENAME=production-daily-20261015T010000Z.dump.enc\n"
f"BACKUP_SHA256={'a' * 64}\n"
f"VERIFIED_AT={verified_at}\n"
"BACKUP_FORMAT=2\n",
)
return record
def run_disk_mode(bin_dir: Path, script_env: dict[str, str]) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[BASH, "-lc", 'export PATH="$1:$PATH"; exec bash "$2" --mode disk', "bash", bash_path(bin_dir), bash_path(HEALTH_SCRIPT)],
cwd=ROOT,
env={**os.environ, **script_env, "JYOTISHA_PYTHON": bash_path(Path(sys.executable))},
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
class HealthHandler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
if self.path != "/health":
self.send_response(404)
self.end_headers()
return
kind = getattr(self.server, "kind", "ok")
if kind == "timeout":
time.sleep(3)
commit = getattr(self.server, "commit", "a" * 40)
if kind == "500":
status, body = 500, b'{"status":"ok"}'
elif kind == "missing":
status, body = 200, b"{}"
elif kind == "degraded":
status, body = 200, json.dumps({"status": "degraded", "deployment": {"gitCommit": commit}}).encode()
else:
status, body = 200, json.dumps({"status": "ok", "deployment": {"gitCommit": commit}}).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt: str, *args: object) -> None:
return
class HookHandler(BaseHTTPRequestHandler):
def do_POST(self) -> None:
length = int(self.headers.get("Content-Length", "0"))
body = self.rfile.read(length)
self.server.bodies.append(body)
self.close_connection = True
code = int(getattr(self.server, "feishu_code", 0))
reply = json.dumps({"code": code}).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(reply)))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(reply)
def log_message(self, fmt: str, *args: object) -> None:
return
def serve(handler: type[BaseHTTPRequestHandler]) -> tuple[ThreadingHTTPServer, threading.Thread]:
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
return server, thread
def alert_lines(state: Path) -> list[str]:
log = state / "alerts.log"
if not log.exists():
return []
return [line for line in log.read_text(encoding="utf-8").splitlines() if line]
def test_daily_workflow_is_scheduled_from_main_and_reuses_recovery() -> None:
text = WORKFLOW.read_text(encoding="utf-8")
assert "\tif:" not in text
assert "if: gitea.ref == 'refs/heads/main'" in text
assert 'cron: "0 20 * * *"' in text
assert "workflow_dispatch:" in text
assert 'git show "$GITEA_SHA:deploy/run-production-recovery.sh"' in text
assert "RECOVERY_BASENAME_PREFIX='production-daily'" in text
assert "prune-production-daily-backups.sh" in text
prune_call = text.index("bash '$incoming/prune-production-daily-backups.sh'")
recovery_call = text.index("bash '$incoming/run-production-recovery.sh'")
assert prune_call < recovery_call
assert "production-daily-" in text
assert 'artifact_retention="${ARTIFACT_RETENTION_DAYS:-14}"' in text
assert 'local_retention="${LOCAL_RETENTION_DAYS:-7}"' in text
assert 'process.env["INPUT_RETENTION-DAYS"]=process.env.ARTIFACT_RETENTION_DAYS' in text
assert "set +x" in text
assert "pg_dump" not in text
assert "CHECK_RELEASE_GATE" not in text
assert "observed_staging_sha" not in text
assert "STAGING_URL" not in text
assert "echo \"$SSH_PRIVATE_KEY" not in text
assert "PRODUCTION_BACKUP_ENCRYPTION_KEY" not in text
recovery = RECOVERY_WORKFLOW.read_text(encoding="utf-8")
assert "observed_staging_sha" in recovery
assert 'process.env["INPUT_RETENTION-DAYS"]="30"' in recovery
def test_recovery_script_keeps_manual_names_unless_daily_prefix() -> None:
text = RECOVERY_SCRIPT.read_text(encoding="utf-8")
assert 'backup_prefix="production-pre-migration"' in text
assert 'production-daily) backup_prefix="production-daily"' in text
assert "openssl enc -aes-256-cbc -salt -pbkdf2" in text
assert "usage_percent < 70" in text
assert "flock -n 9" in text
assert '[ "$DEPLOY_PATH" = "/opt/jyotisha-production" ]' in text
assert 'echo "baseline_updated=false" >&2' in text
assert 'echo "baseline_updated=true" >&2' in text
assert "daily-backup-latest.env" in text
def install_fake_recovery_bin(bin_dir: Path) -> None:
bin_dir.mkdir()
write_text(
bin_dir / "sudo",
"#!/usr/bin/env bash\nset -euo pipefail\nif [[ \"${1:-}\" == \"-n\" ]]; then shift; fi\nexec \"$@\"\n",
)
write_text(
bin_dir / "df",
"#!/usr/bin/env bash\nprintf '%s\\n' 'Filesystem 1024-blocks Used Available Capacity Mounted' 'tmpfs 100 10 90 10% /'\n",
)
# Windows cannot chmod pytest's temp directories. These stand-ins only
# exist on the test PATH. The production script still calls the real commands.
write_text(
bin_dir / "chmod",
"#!/usr/bin/env bash\nexit 0\n",
)
# Git Bash on this Windows machine has no flock. The production script still calls it.
write_text(
bin_dir / "flock",
"#!/usr/bin/env bash\nexit 0\n",
)
write_text(
bin_dir / "install",
"""#!/usr/bin/env bash
set -euo pipefail
dirs=()
while [[ $# -gt 0 ]]; do
case "$1" in
-d) shift ;;
-m) shift; shift ;;
--) shift; break ;;
-*) shift ;;
*) dirs+=("$1"); shift ;;
esac
done
mkdir -p "${dirs[@]}"
""",
)
write_text(
bin_dir / "docker",
"""#!/usr/bin/env bash
set -euo pipefail
cmd="${1:-}"
shift || true
case "$cmd" in
ps)
printf '%s\\n' fixturecontainer
;;
inspect)
printf '%s\\n' sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
;;
run)
exit 0
;;
exec)
tool=""
sql=""
prev=""
for arg in "$@"; do
if [[ "$prev" == "-c" ]]; then
sql="$arg"
fi
case "$arg" in
pg_dump|pg_restore|psql) tool="$arg" ;;
esac
prev="$arg"
done
case "$tool" in
pg_dump)
printf '%s\\n' fixture-dump
;;
pg_restore)
cat >/dev/null
;;
psql)
case "$sql" in
*role_table_grants*) printf '%s\\n' 'reader|SELECT|profiles' ;;
*column_privileges*) printf '%s\\n' 'reader|SELECT|profiles|id' ;;
*pg_get_userbyid*) printf '%s\\n' 'profiles|app' ;;
*identity.users*) printf '%s\\n' '1|2|3|4' ;;
*"CREATE DATABASE"*|*"DROP DATABASE"*) ;;
*)
printf 'unexpected psql\\n' >&2
exit 1
;;
esac
;;
*)
printf 'unexpected docker exec\\n' >&2
exit 1
;;
esac
;;
*)
printf 'unexpected docker %s\\n' "$cmd" >&2
exit 1
;;
esac
""",
)
subprocess.run(
[BASH, "-lc", 'chmod +x "$1/sudo" "$1/df" "$1/docker" "$1/chmod" "$1/install" "$1/flock"', "chmod", bash_path(bin_dir)],
check=True,
)
def write_recovery_env(deploy: Path) -> None:
write_text(
deploy / ".env.production.database",
"\n".join(
[
"POSTGRES_DB=fixturedatabase",
"POSTGRES_USER=fixtureuser",
"POSTGRES_PASSWORD=fixturepassword",
"PRODUCTION_BACKUP_ENCRYPTION_KEY=fixtureencryptionkey",
"",
]
),
)
def run_recovery(deploy: Path, bin_dir: Path, prefix: str | None) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env.pop("FEISHU_WEBHOOK_URL", None)
env.pop("FEISHU_SIGN_SECRET", None)
env.pop("RECOVERY_BASENAME_PREFIX", None)
env["DEPLOY_PATH"] = bash_path(deploy)
env["RECOVERY_RUN_ID"] = "4242"
env["JYOTISHA_RECOVERY_FIXTURE"] = "1"
if prefix:
env["RECOVERY_BASENAME_PREFIX"] = prefix
return subprocess.run(
[
BASH,
"-lc",
'export PATH="$1:$PATH"; exec bash "$2"',
"bash",
bash_path(bin_dir),
bash_path(RECOVERY_SCRIPT),
],
cwd=ROOT,
env=env,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
def parse_env(text: str) -> dict[str, str]:
parsed: dict[str, str] = {}
for line in text.splitlines():
key, value = line.split("=", 1)
parsed[key] = value
return parsed
def test_daily_backup_does_not_replace_the_recovery_point(tmp_path: Path) -> None:
deploy = tmp_path / "deploy"
state = deploy / ".state"
state.mkdir(parents=True)
write_recovery_env(deploy)
baseline = state / "recovery-baseline.env"
original = (
"RECOVERY_REFERENCE=gitea-actions-run-1/production-pre-migration-20261014T120000Z.dump.enc\n"
"RECOVERY_CREATED_AT=2026-10-14T12:00:00Z\n"
"RECOVERY_SHA256=" + ("ab" * 32) + "\n"
"RESTORE_VERIFIED=true\n"
"RECOVERY_FORMAT=2\n"
"VERIFY_FILE=production-pre-migration-20261014T120000Z-restore-verify.json\n"
)
write_text(baseline, original)
subprocess.run(
[BASH, "-lc", 'touch -d "@$1" "$2"', "touch", "1700000000", bash_path(baseline)],
check=True,
)
before_ns = baseline.stat().st_mtime_ns
before_bytes = baseline.read_bytes()
bin_dir = tmp_path / "bin"
install_fake_recovery_bin(bin_dir)
completed = run_recovery(deploy, bin_dir, "production-daily")
assert completed.returncode == 0, completed.stderr
assert baseline.read_bytes() == before_bytes
assert baseline.stat().st_mtime_ns == before_ns
assert "baseline_updated" not in completed.stdout
assert "baseline_updated=false" in completed.stderr.splitlines()
assert "baseline_updated=true" not in completed.stderr
assert len(completed.stdout.splitlines()) == 7
stdout_env = parse_env(completed.stdout)
assert stdout_env["BACKUP_BASENAME"].startswith("production-daily-")
daily = state / "daily-backup-latest.env"
daily_env = parse_env(daily.read_text(encoding="utf-8"))
assert set(daily_env) == {"BACKUP_BASENAME", "BACKUP_SHA256", "VERIFIED_AT", "BACKUP_FORMAT"}
assert daily_env["BACKUP_FORMAT"] == "2"
assert daily_env["BACKUP_BASENAME"] == stdout_env["BACKUP_BASENAME"]
assert daily_env["BACKUP_SHA256"] == stdout_env["RECOVERY_SHA256"]
daily_text = daily.read_text(encoding="utf-8")
assert "fixturepassword" not in daily_text
assert "fixtureencryptionkey" not in daily_text
assert "fixturedatabase" not in daily_text
verify = deploy / "backups" / stdout_env["VERIFY_BASENAME"]
payload = json.loads(verify.read_text(encoding="utf-8"))
assert payload["checks"]["table_grants_match"] is True
assert payload["checks"]["column_privileges_match"] is True
assert payload["checks"]["table_owners_match"] is True
assert "fixturepassword" not in verify.read_text(encoding="utf-8")
def test_manual_recovery_still_writes_format_two(tmp_path: Path) -> None:
deploy = tmp_path / "deploy"
state = deploy / ".state"
state.mkdir(parents=True)
write_recovery_env(deploy)
sentinel = state / "daily-backup-latest.env"
write_text(sentinel, "BACKUP_BASENAME=keep\nBACKUP_FORMAT=2\n")
before = sentinel.read_bytes()
bin_dir = tmp_path / "bin"
install_fake_recovery_bin(bin_dir)
completed = run_recovery(deploy, bin_dir, None)
assert completed.returncode == 0, completed.stderr
assert "baseline_updated=true" in completed.stderr.splitlines()
assert "baseline_updated=false" not in completed.stderr
assert len(completed.stdout.splitlines()) == 7
baseline_text = (state / "recovery-baseline.env").read_text(encoding="utf-8")
baseline = parse_env(baseline_text)
assert baseline["RECOVERY_FORMAT"] == "2"
assert baseline["VERIFY_FILE"].startswith("production-pre-migration-")
assert baseline["RECOVERY_REFERENCE"].startswith("gitea-actions-run-4242/production-pre-migration-")
assert "fixturepassword" not in baseline_text
assert "fixtureencryptionkey" not in baseline_text
assert parse_env(completed.stdout)["BACKUP_BASENAME"].startswith("production-pre-migration-")
assert sentinel.read_bytes() == before
def test_release_migration_and_rollback_ignore_daily_backup_record() -> None:
readers = [
ROOT / ".gitea" / "workflows" / "deploy-production.yml",
ROOT / ".gitea" / "workflows" / "migrate-production-database.yml",
ROOT / "deploy" / "run-production-deploy.sh",
ROOT / "deploy" / "run-production-migration.sh",
]
for path in readers:
text = path.read_text(encoding="utf-8")
assert "daily-backup-latest.env" not in text, path.name
assert "recovery-baseline.env" in text
for path in (WORKFLOW, RECOVERY_WORKFLOW):
text = path.read_text(encoding="utf-8")
assert "JYOTISHA_RECOVERY_FIXTURE" not in text
assert '[[ "$(wc -l <"$ssh_root/recovery-output.env" | tr -d \' \')" == 7 ]]' in text
def test_prune_deletes_only_expired_daily_files(tmp_path: Path) -> None:
backup = tmp_path / "backups"
nested = backup / "nested"
nested.mkdir(parents=True)
now = 1_700_000_000
limit = 7 * 86400
names = {
"production-daily-20261001T000000Z.dump.enc": now - limit - 60,
"production-daily-20261001T000000Z-restore-verify.json": now - limit - 60,
"production-daily-20261007T000000Z.dump.enc": now - limit,
"production-daily-20261007T000000Z-restore-verify.json": now - limit,
"production-daily-20261014T000000Z.dump.enc": now - 3600,
"production-daily-20261014T000000Z-restore-verify.json": now - 3600,
"production-pre-migration-20261001T000000Z.dump.enc": now - limit - 3600,
"production-pre-migration-20261001T000000Z-restore-verify.json": now - limit - 3600,
"production-daily-notes.txt": now - limit - 3600,
"production-daily-20261001T000000Z.dump.enc.bak": now - limit - 3600,
"unrelated.txt": now - limit - 3600,
}
for name, epoch in names.items():
path = backup / name
write_text(path, "fictional\n")
subprocess.run(
[BASH, "-lc", 'touch -d "@$1" "$2"', "touch", str(epoch), bash_path(path)],
check=True,
)
write_text(nested / "production-daily-20261001T000000Z.dump.enc", "nested\n")
completed = run_bash(PRUNE_SCRIPT, {}, [bash_path(backup), "7", str(now)])
assert completed.returncode == 0, completed.stderr
deleted = completed.stdout.splitlines()
assert deleted == ["production-daily-20261001T000000Z.dump.enc"]
assert not (backup / "production-daily-20261001T000000Z.dump.enc").exists()
assert not (backup / "production-daily-20261001T000000Z-restore-verify.json").exists()
for name in names:
if name.startswith("production-daily-20261001T000000Z.dump") or name.startswith(
"production-daily-20261001T000000Z-restore"
):
continue
assert (backup / name).exists(), name
assert (nested / "production-daily-20261001T000000Z.dump.enc").exists()
def test_health_alerts_after_three_failures_and_recovery_once(tmp_path: Path) -> None:
server, _thread = serve(HealthHandler)
server.kind = "500"
state = tmp_path / "state"
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_HEALTH_URL": f"http://127.0.0.1:{server.server_address[1]}/health",
"JYOTISHA_HEALTH_TIMEOUT": "2",
}
try:
for _ in range(2):
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
assert alert_lines(state) == []
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
lines = alert_lines(state)
assert len(lines) == 1
assert "errorCode=health_down" in lines[0]
assert "count=3" in lines[0]
assert "route=/api/health" in lines[0]
server.kind = "ok"
server.commit = "b" * 40
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
lines = alert_lines(state)
assert len(lines) == 2
assert "errorCode=recovered" in lines[1]
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
assert len(alert_lines(state)) == 2
finally:
server.shutdown()
def test_health_timeout_and_missing_fields_count_as_failures(tmp_path: Path) -> None:
server, _thread = serve(HealthHandler)
server.kind = "timeout"
state = tmp_path / "state"
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_HEALTH_URL": f"http://127.0.0.1:{server.server_address[1]}/health",
"JYOTISHA_HEALTH_TIMEOUT": "1",
}
try:
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
assert alert_lines(state) == []
server.kind = "missing"
run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
assert any("errorCode=health_down" in line for line in alert_lines(state))
finally:
server.shutdown()
def test_commit_change_alerts_once_outside_release_window(tmp_path: Path) -> None:
server, _thread = serve(HealthHandler)
server.kind = "ok"
server.commit = "a" * 40
state = tmp_path / "state"
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_HEALTH_URL": f"http://127.0.0.1:{server.server_address[1]}/health",
"JYOTISHA_HEALTH_TIMEOUT": "2",
}
try:
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
assert alert_lines(state) == []
server.commit = "b" * 40
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
lines = alert_lines(state)
assert len(lines) == 1
assert "errorCode=commit_changed" in lines[0]
assert "b" * 40 not in lines[0]
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
assert len(alert_lines(state)) == 1
marker = state / "release-window"
state.mkdir(parents=True, exist_ok=True)
write_text(marker, "open\n")
server.commit = "c" * 40
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
assert len(alert_lines(state)) == 1
finally:
server.shutdown()
def test_disk_alerts_once_and_no_webhook_stays_local(tmp_path: Path) -> None:
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
write_text(
bin_dir / "df",
"#!/usr/bin/env bash\nprintf '%s\\n' 'Filesystem 1024-blocks Used Available Capacity Mounted' \"overlay 100 90 10 ${FAKE_DISK_PERCENT:?}% /\"\n",
)
subprocess.run([BASH, "-lc", 'chmod +x "$1/df"', "chmod", bash_path(bin_dir)], check=True)
state = tmp_path / "state"
record = write_backup_record(tmp_path, "2026-10-15T01:00:00Z")
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_DAILY_BACKUP_RECORD": bash_path(record),
"JYOTISHA_NOW": "2026-10-15T01:00:00Z",
"FAKE_DISK_PERCENT": "81",
}
env.pop("FEISHU_WEBHOOK_URL", None)
env.pop("FEISHU_SIGN_SECRET", None)
completed = run_disk_mode(bin_dir, env)
assert completed.returncode == 0, completed.stderr
assert "errorCode=disk_high" in alert_lines(state)[0]
assert "count=81" in alert_lines(state)[0]
env["FAKE_DISK_PERCENT"] = "81"
again = run_disk_mode(bin_dir, env)
assert again.returncode == 0, again.stderr
assert len(alert_lines(state)) == 1
env["FAKE_DISK_PERCENT"] = "10"
recovered = run_disk_mode(bin_dir, env)
assert recovered.returncode == 0, recovered.stderr
assert len(alert_lines(state)) == 2
assert "errorCode=recovered" in alert_lines(state)[1]
def test_feishu_payload_keeps_only_allowed_fields(tmp_path: Path) -> None:
hook, _thread = serve(HookHandler)
hook.bodies = []
health, _thread = serve(HealthHandler)
health.kind = "500"
state = tmp_path / "state"
secret = "fixture-sign-secret"
env_file = tmp_path / "monitor.env"
write_text(env_file, f"FEISHU_WEBHOOK_URL=http://127.0.0.1:{hook.server_address[1]}/hook\nFEISHU_SIGN_SECRET={secret}\n")
os.chmod(env_file, 0o600)
if os.name != "nt":
assert (env_file.stat().st_mode & 0o777) == 0o600
notify = NOTIFY_SCRIPT.read_text(encoding="utf-8")
assert '[ "$mode" = "600" ]' in notify
assert "curl --silent --show-error --fail" in notify
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(env_file),
"JYOTISHA_HEALTH_URL": f"http://127.0.0.1:{health.server_address[1]}/health",
"JYOTISHA_HEALTH_TIMEOUT": "2",
}
try:
for _ in range(3):
completed = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert completed.returncode == 0, completed.stderr
assert len(hook.bodies) == 1
payload = json.loads(hook.bodies[0].decode("utf-8"))
assert set(payload) == {"timestamp", "sign", "msg_type", "content"}
assert payload["msg_type"] == "text"
text = payload["content"]["text"]
assert secret not in text
assert secret not in hook.bodies[0].decode("utf-8")
assert "errorCode=health_down" in text
assert "@" not in text
expected = subprocess.run(
[
sys.executable,
"-c",
"import base64,hashlib,hmac,os,sys; ts=sys.argv[1]; secret=sys.argv[2]; key=f'{ts}\\n{secret}'.encode(); print(base64.b64encode(hmac.new(key, digestmod=hashlib.sha256).digest()).decode())",
payload["timestamp"],
secret,
],
check=True,
capture_output=True,
text=True,
).stdout.strip()
assert payload["sign"] == expected
finally:
hook.shutdown()
health.shutdown()
def test_feishu_rejection_is_not_recorded_as_sent(tmp_path: Path) -> None:
hook, _thread = serve(HookHandler)
hook.bodies = []
hook.feishu_code = 19001
health, _thread = serve(HealthHandler)
health.kind = "500"
state = tmp_path / "state"
env_file = tmp_path / "monitor.env"
write_text(env_file, f"FEISHU_WEBHOOK_URL=http://127.0.0.1:{hook.server_address[1]}/hook\n")
os.chmod(env_file, 0o600)
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(env_file),
"JYOTISHA_HEALTH_URL": f"http://127.0.0.1:{health.server_address[1]}/health",
"JYOTISHA_HEALTH_TIMEOUT": "2",
}
try:
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
assert run_bash(HEALTH_SCRIPT, env, ["--mode", "health"]).returncode == 0
rejected = run_bash(HEALTH_SCRIPT, env, ["--mode", "health"])
assert rejected.returncode != 0
assert len(hook.bodies) == 1
assert alert_lines(state) == []
assert "health_down" not in rejected.stdout
finally:
hook.shutdown()
health.shutdown()
def uuid(index: int) -> str:
return f"00000000-0000-4000-8000-{index:012d}"
def test_error_digest_drops_personal_text_and_respects_thresholds(tmp_path: Path) -> None:
safe = [
f"2026-10-15T00:00:{index:02d}Z web [agent-observability] {json.dumps({'requestId': uuid(index), 'errorCode': 'settlement_failed'})}"
for index in range(1, 11)
]
unsafe = [
"2026-10-15T00:01:00Z web person@example.com errorCode=should_drop",
"2026-10-15T00:01:01Z web note 1900-01-01 birth_date",
"2026-10-15T00:01:02Z web 对话内容 今天工作不顺",
]
fixture = tmp_path / "logs.txt"
write_text(fixture, "\n".join([*safe, *unsafe]) + "\n")
state = tmp_path / "state"
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_LOG_FIXTURE": bash_path(fixture),
"JYOTISHA_NOW": "2026-10-15T01:00:00Z",
}
below = tmp_path / "below.txt"
write_text(below, "\n".join(safe[:9]) + "\n")
quiet = run_bash(DIGEST_SCRIPT, {**env, "JYOTISHA_LOG_FIXTURE": bash_path(below)}, ["--window"])
assert quiet.returncode == 0, quiet.stderr
assert alert_lines(state) == []
completed = run_bash(DIGEST_SCRIPT, env, ["--window"])
assert completed.returncode == 0, completed.stderr
lines = alert_lines(state)
assert len(lines) == 1
assert "errorCode=settlement_failed" in lines[0]
assert "count=10" in lines[0]
assert lines[0].count(",") == 4
assert uuid(6) not in lines[0]
assert "person@example.com" not in lines[0]
assert "1900-01-01" not in lines[0]
assert "今天工作不顺" not in lines[0]
assert "should_drop" not in lines[0]
soon = run_bash(DIGEST_SCRIPT, {**env, "JYOTISHA_NOW": "2026-10-15T01:29:59Z"}, ["--window"])
assert soon.returncode == 0, soon.stderr
assert len(alert_lines(state)) == 1
later = run_bash(DIGEST_SCRIPT, {**env, "JYOTISHA_NOW": "2026-10-15T01:30:00Z"}, ["--window"])
assert later.returncode == 0, later.stderr
assert len(alert_lines(state)) == 2
assert "errorCode=settlement_failed" in alert_lines(state)[1]
fivexx = tmp_path / "fivexx.txt"
write_text(fivexx, "\n".join(f"2026-10-15T00:02:{index:02d}Z api status=502" for index in range(20)) + "\n")
state_5xx = tmp_path / "state5"
completed = run_bash(
DIGEST_SCRIPT,
{**env, "JYOTISHA_MONITOR_STATE_DIR": bash_path(state_5xx), "JYOTISHA_LOG_FIXTURE": bash_path(fivexx)},
["--window"],
)
assert completed.returncode == 0, completed.stderr
joined = "\n".join(alert_lines(state_5xx))
assert "errorCode=http_5xx" in joined
assert "count=20" in joined
def test_daily_digest_summarizes_previous_shanghai_day(tmp_path: Path) -> None:
fixture = tmp_path / "daily.txt"
write_text(
fixture,
"\n".join(
[
'2026-10-14T02:00:00Z api [agent-observability] {"requestId":"00000000-0000-4000-8000-000000000001","errorCode":"settlement_failed"}',
'2026-10-15T01:30:00Z api [agent-observability] {"requestId":"00000000-0000-4000-8000-000000000002","errorCode":"settlement_failed"}',
]
)
+ "\n",
)
state = tmp_path / "state"
completed = run_bash(
DIGEST_SCRIPT,
{
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_LOG_FIXTURE": bash_path(fixture),
"JYOTISHA_NOW": "2026-10-15T01:00:00Z",
},
["--daily"],
)
assert completed.returncode == 0, completed.stderr
lines = alert_lines(state)
assert len(lines) == 1
assert "count=1" in lines[0]
assert "00000000-0000-4000-8000-000000000001" in lines[0]
assert "00000000-0000-4000-8000-000000000002" not in lines[0]
def test_find_request_log_sorts_and_rejects_injection(tmp_path: Path) -> None:
log_dir = tmp_path / "logs"
log_dir.mkdir()
request_id = "11111111-2222-4333-8444-555555555555"
write_text(log_dir / "caddy.log", f"2026-10-15T03:00:00Z caddy {request_id}\n")
write_text(log_dir / "web.log", f"2026-10-15T01:00:00Z web {request_id}\nignore\n")
write_text(log_dir / "api.log", f"2026-10-15T02:00:00Z api {request_id}\n")
canary = tmp_path / "canary.txt"
write_text(canary, "keep\n")
found = run_bash(FIND_SCRIPT, {"JYOTISHA_LOG_DIR": bash_path(log_dir)}, [request_id, "--since", "24h"])
assert found.returncode == 0, found.stderr
assert found.stdout.splitlines() == [
f"2026-10-15T01:00:00Z web {request_id}",
f"2026-10-15T02:00:00Z api {request_id}",
f"2026-10-15T03:00:00Z caddy {request_id}",
]
rejected = run_bash(FIND_SCRIPT, {"JYOTISHA_LOG_DIR": bash_path(log_dir)}, ["; rm -rf /", "--since", "24h"])
assert rejected.returncode == 2
assert canary.read_text(encoding="utf-8") == "keep\n"
injected = run_bash(FIND_SCRIPT, {"JYOTISHA_LOG_DIR": bash_path(log_dir)}, ["$(rm canary.txt)", "--since", "1h"])
assert injected.returncode == 2
assert canary.exists()
bad_since = run_bash(FIND_SCRIPT, {"JYOTISHA_LOG_DIR": bash_path(log_dir)}, [request_id, "--since", "24h;rm"])
assert bad_since.returncode == 2
compose_log = tmp_path / "compose.log"
write_text(
compose_log,
"\n".join(
[
f"web-1 | 2026-10-15T01:00:00Z web {request_id}",
f"api-1 | 2026-10-15T03:00:00Z api {request_id}",
f"caddy-1 | 2026-10-15T02:00:00Z caddy {request_id}",
f"api-1 | ignore {request_id}",
]
)
+ "\n",
)
composed = run_bash(
FIND_SCRIPT,
{"JYOTISHA_COMPOSE_LOG_FIXTURE": bash_path(compose_log)},
[request_id, "--since", "24h"],
)
assert composed.returncode == 0, composed.stderr
assert composed.stdout.splitlines() == [
f"2026-10-15T01:00:00Z web {request_id}",
f"2026-10-15T02:00:00Z caddy {request_id}",
f"2026-10-15T03:00:00Z api {request_id}",
]
def service_logging_blocks(text: str) -> dict[str, str]:
blocks: dict[str, str] = {}
service: str | None = None
in_services = False
capturing = False
buf: list[str] = []
for line in text.splitlines():
if line.startswith("services:"):
in_services = True
continue
if in_services and line and not line.startswith(" "):
in_services = False
if not in_services:
continue
if line.startswith(" ") and not line.startswith(" ") and line.endswith(":"):
if capturing and service is not None:
blocks[service] = "\n".join(buf)
service = line.strip()[:-1]
capturing = False
buf = []
continue
if service and line == " logging:":
capturing = True
buf = [line]
continue
if capturing:
if line.startswith(" ") and not line.startswith(" "):
blocks[service] = "\n".join(buf)
capturing = False
buf = []
else:
buf.append(line)
if capturing and service is not None:
blocks[service] = "\n".join(buf)
return blocks
def test_compose_logging_is_present_and_identical() -> None:
expected = "\n".join(
[
" logging:",
" driver: json-file",
" options:",
' max-size: "20m"',
' max-file: "10"',
]
)
expected_names = {
SERVER_COMPOSE: {"api", "web", "caddy"},
POSTGRES_COMPOSE: {"postgres", "migrator", "migration-checker"},
}
for path, names in expected_names.items():
blocks = service_logging_blocks(path.read_text(encoding="utf-8"))
assert set(blocks) == names
for name in names:
assert blocks[name] == expected, name
def test_monitor_commands_use_production_docker() -> None:
for script in (DIGEST_SCRIPT, FIND_SCRIPT):
text = script.read_text(encoding="utf-8")
assert "sudo -n docker compose -p jyotisha-production" in text
assert "export DATABASE_ENV_FILE='../.env.production.database'" in text
assert "export APP_ENV_FILE='../.env.production'" in text
assert "docker-compose.server.yml" in text
assert "docker-compose.postgres.yml" in text
assert "docker-compose.production.yml" in text
for line in text.splitlines():
stripped = line.strip()
if stripped.startswith("docker ") or stripped.startswith("docker compose"):
raise AssertionError(stripped)
finder = FIND_SCRIPT.read_text(encoding="utf-8")
strip_at = finder.index("sed -E 's/^[^|]*\\| //'")
sort_at = finder.index("sort", strip_at)
assert strip_at < sort_at
def test_safety_net_is_on_the_quick_gate() -> None:
text = (ROOT / "scripts" / "run_quality_gate.py").read_text(encoding="utf-8")
assert '"tests/test_beta_safety_net.py"' in text
def install_fake_df(bin_dir: Path) -> None:
bin_dir.mkdir()
write_text(
bin_dir / "df",
"#!/usr/bin/env bash\nprintf '%s\\n' 'Filesystem 1024-blocks Used Available Capacity Mounted' \"overlay 100 10 90 ${FAKE_DISK_PERCENT:?}% /\"\n",
)
subprocess.run([BASH, "-lc", 'chmod +x "$1/df"', "chmod", bash_path(bin_dir)], check=True)
def test_stale_daily_backup_alerts_once_until_a_fresh_record(tmp_path: Path) -> None:
bin_dir = tmp_path / "bin"
install_fake_df(bin_dir)
state = tmp_path / "state"
now = "2026-10-15T12:00:00Z"
env = {
"JYOTISHA_MONITOR_STATE_DIR": bash_path(state),
"JYOTISHA_MONITOR_ENV": bash_path(tmp_path / "missing.env"),
"JYOTISHA_NOW": now,
"JYOTISHA_DAILY_BACKUP_RECORD": bash_path(tmp_path / "missing-record.env"),
"FAKE_DISK_PERCENT": "10",
}
missing = run_disk_mode(bin_dir, env)
assert missing.returncode == 0, missing.stderr
assert alert_lines(state) == [
line for line in alert_lines(state) if "errorCode=backup_stale" in line and "count=36" in line
]
assert len(alert_lines(state)) == 1
assert "production-daily" not in alert_lines(state)[0]
assert ".env" not in alert_lines(state)[0]
again = run_disk_mode(bin_dir, env)
assert again.returncode == 0, again.stderr
assert len(alert_lines(state)) == 1
exact = tmp_path / "exact"
exact.mkdir()
env["JYOTISHA_MONITOR_STATE_DIR"] = bash_path(exact)
env["JYOTISHA_DAILY_BACKUP_RECORD"] = bash_path(write_backup_record(exact, "2026-10-14T00:00:00Z"))
boundary = run_disk_mode(bin_dir, env)
assert boundary.returncode == 0, boundary.stderr
assert alert_lines(exact) == []
stale_state = tmp_path / "stale-state"
old_dir = tmp_path / "old"
old_dir.mkdir()
env["JYOTISHA_MONITOR_STATE_DIR"] = bash_path(stale_state)
env["JYOTISHA_DAILY_BACKUP_RECORD"] = bash_path(write_backup_record(old_dir, "2026-10-13T23:00:00Z"))
old = run_disk_mode(bin_dir, env)
assert old.returncode == 0, old.stderr
assert len(alert_lines(stale_state)) == 1
assert "errorCode=backup_stale" in alert_lines(stale_state)[0]
assert "count=37" in alert_lines(stale_state)[0]
fresh_dir = tmp_path / "fresh"
fresh_dir.mkdir()
env["JYOTISHA_DAILY_BACKUP_RECORD"] = bash_path(write_backup_record(fresh_dir, now))
recovered = run_disk_mode(bin_dir, env)
assert recovered.returncode == 0, recovered.stderr
assert len(alert_lines(stale_state)) == 2
assert "errorCode=recovered" in alert_lines(stale_state)[1]
def test_shell_scripts_pass_bash_n() -> None:
for script in (PRUNE_SCRIPT, HEALTH_SCRIPT, DIGEST_SCRIPT, FIND_SCRIPT, NOTIFY_SCRIPT, RECOVERY_SCRIPT):
completed = subprocess.run(
[BASH, "-n", bash_path(script)],
capture_output=True,
text=True,
encoding="utf-8",
)
assert completed.returncode == 0, script.name + completed.stderr