teleo-infrastructure/hermes-agent/patches/apply_telegram_sender_name_policy.py
2026-07-15 22:49:20 +02:00

104 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Bind Hermes Telegram participant names to the current update's visible handle."""
from __future__ import annotations
import argparse
import ast
import hashlib
import json
import os
import tempfile
from pathlib import Path
PATCH_VERSION = "teleo-telegram-visible-handle-name-policy-v1"
PATCH_MARKER = " # TELEO_TELEGRAM_VISIBLE_HANDLE_NAME_POLICY_V1\n"
SOURCE_ANCHOR = " user_name=user.full_name if user else None,\n"
REPLACEMENT = ''' # TELEO_TELEGRAM_VISIBLE_HANDLE_NAME_POLICY_V1
# The current Telegram update is authoritative for the visible
# handle. The stable Telegram user_id continues to own session
# isolation; profile full_name is only a fallback when no public
# username exists on this update.
user_name=(
(user.username or "").strip().lstrip("@")
if user and (user.username or "").strip()
else user.full_name if user else None
),
'''
def sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def patch_source(source: str) -> tuple[str, bool]:
if PATCH_MARKER in source:
required = (
'(user.username or "").strip().lstrip("@")',
"else user.full_name if user else None",
"user_id=str(user.id) if user else None",
)
missing = [item for item in required if item not in source]
if missing:
raise ValueError(f"installed Telegram name-policy marker is incomplete: {missing}")
if SOURCE_ANCHOR in source:
raise ValueError("installed Telegram name-policy marker still contains the stale full-name anchor")
return source, False
if source.count(SOURCE_ANCHOR) != 1:
raise ValueError("Hermes Telegram sender construction changed; refusing an unreviewed name-policy patch")
patched = source.replace(SOURCE_ANCHOR, REPLACEMENT, 1)
ast.parse(patched)
return patched, True
def install(path: Path, *, check: bool) -> tuple[dict[str, str | bool], int]:
source = path.read_text(encoding="utf-8")
patched, changed = patch_source(source)
before_sha = sha256_text(source)
after_sha = sha256_text(patched)
if check:
return {
"patch_version": PATCH_VERSION,
"status": "patch_required" if changed else "installed",
"target": str(path),
"sha256": before_sha,
}, 1 if changed else 0
if changed:
mode = path.stat().st_mode & 0o777
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False
) as handle:
handle.write(patched)
temp_path = Path(handle.name)
try:
temp_path.chmod(mode)
os.replace(temp_path, path)
finally:
temp_path.unlink(missing_ok=True)
ast.parse(path.read_text(encoding="utf-8"))
return {
"patch_version": PATCH_VERSION,
"status": "installed_now" if changed else "already_installed",
"target": str(path),
"before_sha256": before_sha,
"after_sha256": after_sha,
}, 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("target", type=Path)
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
result, code = install(args.target, check=args.check)
print(json.dumps(result, sort_keys=True))
return code
if __name__ == "__main__":
raise SystemExit(main())