From 692ec851261084ee207052267f3c153e8b88e0d3 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 23 Oct 2026 09:30:32 +0800 Subject: [PATCH 1/3] fix(ci): satisfy Python quality gate --- deploy/check_home_seo.py | 56 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/deploy/check_home_seo.py b/deploy/check_home_seo.py index 07ba705f..c585b3cf 100644 --- a/deploy/check_home_seo.py +++ b/deploy/check_home_seo.py @@ -1,6 +1,5 @@ """Verify homepage metadata without changing Markdown or non-home resources.""" -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path + import socket import subprocess import tempfile @@ -8,9 +7,13 @@ import time import urllib.error import urllib.request +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path HTML = b'msgPublic home' MARKDOWN = b'# msg\n\nAgent-readable public home.\n' + + class Backend(BaseHTTPRequestHandler): def do_GET(self): browser = 'text/html' in self.headers.get('Accept', '') @@ -19,41 +22,58 @@ def do_GET(self): self.send_header('Content-Security-Policy', "default-src 'none'") self.end_headers() self.wfile.write(HTML if browser else MARKDOWN) + def log_message(self, *args): pass + + backend = ThreadingHTTPServer(('127.0.0.1', 0), Backend) thread = threading.Thread(target=backend.serve_forever, daemon=True) thread.start() with socket.socket() as sock: sock.bind(('127.0.0.1', 0)) port = sock.getsockname()[1] + + def get(path, accept): - with urllib.request.urlopen(urllib.request.Request( - f'http://127.0.0.1:{port}{path}', headers={'Accept': accept}), timeout=3) as response: + with urllib.request.urlopen( + urllib.request.Request(f'http://127.0.0.1:{port}{path}', headers={'Accept': accept}), + timeout=3, + ) as response: return response.headers, response.read() + + try: with tempfile.TemporaryDirectory(prefix='msg-home-seo-') as temp: directory = Path(temp) conf = directory / 'nginx.conf' snippet = Path(__file__).with_name('nginx-home-seo.conf').read_text() - conf.write_text(f'pid {directory}/nginx.pid; error_log {directory}/error.log; daemon off; master_process off; events {{}}\n' - 'http { access_log off;\n' - + ''.join(f'{kind}_temp_path {directory}/{kind};\n' for kind in ['client_body','proxy','fastcgi','uwsgi','scgi']) - + f'upstream msg_lmm_best {{ server 127.0.0.1:{backend.server_port}; }}\n' - 'map $uri $msg_browser_cache_control { default "private, no-cache"; }\n' - 'map $uri $msg_browser_referrer_policy { default "no-referrer"; }\n' - + f'server {{ listen 127.0.0.1:{port};\n' + snippet - + 'location / { proxy_pass http://msg_lmm_best; }\n}}\n') - process = subprocess.Popen(['nginx', '-p', temp, '-c', str(conf)], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + conf.write_text( + f'pid {directory}/nginx.pid; error_log {directory}/error.log; daemon off; master_process off; events {{}}\n' + 'http { access_log off;\n' + + ''.join( + f'{kind}_temp_path {directory}/{kind};\n' + for kind in ['client_body', 'proxy', 'fastcgi', 'uwsgi', 'scgi'] + ) + + f'upstream msg_lmm_best {{ server 127.0.0.1:{backend.server_port}; }}\n' + 'map $uri $msg_browser_cache_control { default "private, no-cache"; }\n' + 'map $uri $msg_browser_referrer_policy { default "no-referrer"; }\n' + + f'server {{ listen 127.0.0.1:{port};\n' + + snippet + + 'location / { proxy_pass http://msg_lmm_best; }\n}}\n' + ) + process = subprocess.Popen( + ['nginx', '-p', temp, '-c', str(conf)], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) try: - for attempt in range(40): + for _attempt in range(40): try: get('/', 'text/html') break except urllib.error.URLError: if process.poll() is not None: - raise RuntimeError(process.communicate()[1].decode()) - time.sleep(.05) + raise RuntimeError(process.communicate()[1].decode()) from None + time.sleep(0.05) else: raise RuntimeError('Nginx did not start') headers, body = get('/', 'text/html') @@ -64,7 +84,9 @@ def get(path, accept): headers, body = get('/', 'text/markdown') assert body == MARKDOWN and headers['Content-Type'].startswith('text/markdown') assert get('/another-resource', 'text/html')[1] == HTML - print('HTML metadata, canonical header, CSP/cache, unchanged Markdown and other resources passed') + print( + 'HTML metadata, canonical header, CSP/cache, unchanged Markdown and other resources passed' + ) finally: if process.poll() is None: process.terminate() From e124dc6629889e71c620033cfb4b85154863c6dc Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Sat, 31 Oct 2026 09:15:19 +0800 Subject: [PATCH 2/3] fix(ci): stabilize Python 3.15 async and recovery clocks --- pyproject.toml | 1 + src/msg/application.py | 6 +++++- tests/conftest.py | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 96b4316c..b5702fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ testpaths = ["tests"] pythonpath = ["src"] markers = ["db: 需要真实 PostgreSQL,包含 fixture 依赖和测试内自建集群"] asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" filterwarnings = ["error"] [tool.ruff] diff --git a/src/msg/application.py b/src/msg/application.py index 78c8656f..760e79a4 100644 --- a/src/msg/application.py +++ b/src/msg/application.py @@ -117,7 +117,11 @@ async def load(self): recovery_window=lambda: self.settings.credential_delivery_recovery_window, ) self.certificates = CertificateValidator( - self.registry, root_certificate, root_public, self.settings.service_url, self.clock + self.registry, + root_certificate, + root_public, + self.settings.service_url, + lambda: self.clock(), ) async with self.metadata.transaction(write=False) as tx: self.runtime_generation.require_current(tx) diff --git a/tests/conftest.py b/tests/conftest.py index 5090732f..f5214c84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ import psycopg import pytest +import pytest_asyncio from psycopg import sql from msg.admin.root import _approve_csr, _provision @@ -39,7 +40,7 @@ def _drop_database(cluster, name): connection.execute(sql.SQL('DROP DATABASE {} WITH (FORCE)').format(sql.Identifier(name))) -@pytest.fixture(scope='session') +@pytest_asyncio.fixture(scope='session', loop_scope='session') async def installation_seed(tmp_path_factory, pg_cluster): directory = tmp_path_factory.mktemp('msg-seed') database = 'msg_seed_' + uuid.uuid4().hex From 70007351fe1872899b7c623b99169e88a47107a6 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Sat, 31 Oct 2026 09:24:00 +0800 Subject: [PATCH 3/3] fix(ci): forward proof validator clock updates --- src/msg/admin/recovery_proof.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/msg/admin/recovery_proof.py b/src/msg/admin/recovery_proof.py index 2b81c8ce..3787043d 100644 --- a/src/msg/admin/recovery_proof.py +++ b/src/msg/admin/recovery_proof.py @@ -564,7 +564,7 @@ async def open_for_proof(config_dir): decode(Certificate, trust['certificate']), unb64(trust['public_key'], limit=32), settings.service_url, - app.clock, + lambda: app.clock(), ) from msg.admin.backups import _regular_tree