mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:07:03 +01:00
Also bumps the 16 meta/requirements pins the freshness gate flagged (anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9, ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml 6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path. The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of which parses zsh (SC2148/SC1071), so no zsh file could be committed since the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The hook's own shfmt pass also staged its reformat of 8 shell files. CI_GREEN_SKIP because main is red on exactly the dependency-freshness finding this commit fixes, which is the documented use of that flag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
169 lines
5.2 KiB
Python
169 lines
5.2 KiB
Python
"""Command line entry point for the SyncYomi integrity guard.
|
|
|
|
Exit codes are the contract; a systemd timer and a human both read them:
|
|
|
|
* ``0`` — payload healthy (or a first-run baseline was recorded)
|
|
* ``1`` — payload collapsed, or it could not be read or decoded
|
|
|
|
A read failure is deliberately *not* a pass. The whole point of the guard is to
|
|
be believed on the day something is wrong, and "the database was missing so I
|
|
said OK" is the fail-open shape that lets a bad copy become the only copy.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
from python_pkg.syncyomi_guard.payload import PayloadError, decode_payload
|
|
from python_pkg.syncyomi_guard.store import (
|
|
StoreError,
|
|
load_baseline,
|
|
read_payload,
|
|
save_baseline,
|
|
write_snapshot,
|
|
)
|
|
from python_pkg.syncyomi_guard.verdict import Status, Thresholds, Verdict, compare
|
|
|
|
_DEFAULT_DB = Path("/home/kuhy/services/syncyomi/config/syncyomi.db")
|
|
_DEFAULT_STATE = Path.home() / ".local/share/syncyomi_guard"
|
|
_DEFAULT_SNAPSHOTS = Path.home() / "services/syncyomi/snapshots"
|
|
_DEFAULT_KEEP = 14
|
|
|
|
|
|
def _notify(title: str, body: str) -> None:
|
|
"""Best-effort desktop notification; never fails the run.
|
|
|
|
The exit code is the real signal. A notification is a convenience, so a
|
|
machine without ``notify-send`` (or a run with no session bus) must not turn
|
|
a healthy check into a failure.
|
|
"""
|
|
binary = shutil.which("notify-send")
|
|
if binary is None:
|
|
return
|
|
try:
|
|
subprocess.run(
|
|
[binary, "--urgency=critical", title, body],
|
|
check=False,
|
|
timeout=10,
|
|
)
|
|
except OSError, subprocess.SubprocessError:
|
|
return
|
|
|
|
|
|
def _build_parser() -> argparse.ArgumentParser:
|
|
"""Construct the argument parser."""
|
|
parser = argparse.ArgumentParser(
|
|
prog="syncyomi-guard",
|
|
description=(
|
|
"Verify the SyncYomi library payload has not silently collapsed, "
|
|
"and snapshot it as a restorable .tachibk when it is healthy."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--db",
|
|
type=Path,
|
|
default=_DEFAULT_DB,
|
|
help=f"SyncYomi SQLite database (default: {_DEFAULT_DB})",
|
|
)
|
|
parser.add_argument(
|
|
"--state-dir",
|
|
type=Path,
|
|
default=_DEFAULT_STATE,
|
|
help=f"where the baseline is stored (default: {_DEFAULT_STATE})",
|
|
)
|
|
parser.add_argument(
|
|
"--snapshot-dir",
|
|
type=Path,
|
|
default=_DEFAULT_SNAPSHOTS,
|
|
help=f"where .tachibk snapshots are written (default: {_DEFAULT_SNAPSHOTS})",
|
|
)
|
|
parser.add_argument(
|
|
"--keep",
|
|
type=int,
|
|
default=_DEFAULT_KEEP,
|
|
help=f"how many snapshots to retain (default: {_DEFAULT_KEEP})",
|
|
)
|
|
parser.add_argument(
|
|
"--max-drop",
|
|
type=float,
|
|
default=Thresholds().max_drop_ratio,
|
|
help="fail if any count drops by more than this fraction (default: 0.10)",
|
|
)
|
|
parser.add_argument(
|
|
"--no-snapshot",
|
|
action="store_true",
|
|
help="check only; do not write a snapshot or update the baseline",
|
|
)
|
|
parser.add_argument(
|
|
"--accept",
|
|
action="store_true",
|
|
help=(
|
|
"record the current payload as the new baseline even if it looks "
|
|
"collapsed (use after a deliberate library purge)"
|
|
),
|
|
)
|
|
return parser
|
|
|
|
|
|
def _report(verdict: Verdict) -> None:
|
|
"""Write the verdict to the appropriate stream.
|
|
|
|
A silent guard is a useless one: the journal line is how a human finds out
|
|
what the timer saw, so the counts are written on every run, not only on
|
|
failures.
|
|
"""
|
|
if verdict.is_failure:
|
|
sys.stderr.write(f"COLLAPSED: {verdict.reason}\n")
|
|
if verdict.previous is not None:
|
|
sys.stderr.write(f" previous: {verdict.previous.describe()}\n")
|
|
sys.stderr.write(f" current: {verdict.current.describe()}\n")
|
|
elif verdict.status is Status.FIRST_RUN:
|
|
sys.stdout.write(f"baseline recorded: {verdict.current.describe()}\n")
|
|
else:
|
|
sys.stdout.write(f"ok: {verdict.reason}\n")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Run the guard, returning the process exit code."""
|
|
args = _build_parser().parse_args(argv)
|
|
|
|
try:
|
|
blob = read_payload(args.db)
|
|
stats = decode_payload(blob)
|
|
except (StoreError, PayloadError) as exc:
|
|
sys.stderr.write(f"FAILED: {exc}\n")
|
|
_notify("SyncYomi guard: cannot verify", str(exc))
|
|
return 1
|
|
|
|
try:
|
|
thresholds = Thresholds(max_drop_ratio=args.max_drop)
|
|
except ValueError as exc:
|
|
sys.stderr.write(f"FAILED: {exc}\n")
|
|
return 1
|
|
|
|
verdict = compare(stats, load_baseline(args.state_dir), thresholds)
|
|
_report(verdict)
|
|
|
|
if verdict.is_failure and not args.accept:
|
|
_notify(
|
|
"SyncYomi library collapsed",
|
|
f"{verdict.reason}. Snapshot NOT overwritten; recover from "
|
|
f"{args.snapshot_dir}.",
|
|
)
|
|
return 1
|
|
|
|
if not args.no_snapshot:
|
|
snapshot = write_snapshot(args.snapshot_dir, blob, args.keep)
|
|
save_baseline(args.state_dir, stats)
|
|
sys.stdout.write(f"snapshot: {snapshot}\n")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|