From 60bcefbca2dc2c3e776bf54a8d0690a0e3d86e8f Mon Sep 17 00:00:00 2001 From: George Lambert Date: Mon, 21 Sep 2026 14:11:29 -0400 Subject: [PATCH] fix: keep global HTTP off the event loop WhatToChange: recommended-changes/2026-09-21-event-loop-and-ledger-perms/ What-to-Change.md What-to-Change-To.md How-to-implement-the-changes.md What was actually done: - Global HTTP stamp and verify run via asyncio.to_thread. - File ledger saves chmod 0600. - .gitignore covers coverage, mypy, logs, and editor directories. What was not done: - (none) --- .gitignore | 9 +++++++ .../Done-and-not-done.md | 11 ++++++++ .../How-to-implement-the-changes.md | 8 ++++++ .../README.md | 26 +++++++++++++++++++ .../What-to-Change-To.md | 7 +++++ .../What-to-Change.md | 16 ++++++++++++ .../git-versions.json | 12 +++++++++ .../git-versions.md | 6 +++++ .../global_client.py | 6 +++-- src/verae_mcp_timestamping_server/ledger.py | 1 + 10 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/Done-and-not-done.md create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/How-to-implement-the-changes.md create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/README.md create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change-To.md create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change.md create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.json create mode 100644 recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.md diff --git a/.gitignore b/.gitignore index 03dba23..9ab9734 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,17 @@ __pycache__/ *.pyc .ruff_cache/ .pytest_cache/ +.mypy_cache/ +.dmypy.json +.coverage +.coverage.* +htmlcov/ dist/ build/ *.egg-info/ .env .env.* +*.log +*.tmp +.idea/ +.vscode/ diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/Done-and-not-done.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/Done-and-not-done.md new file mode 100644 index 0000000..401d721 --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/Done-and-not-done.md @@ -0,0 +1,11 @@ +# Done and not done + +WhatToChange: recommended-changes/2026-09-21-event-loop-and-ledger-perms/ + +What was actually done: +- Global HTTP stamp and verify run via asyncio.to_thread. +- File ledger saves chmod 0600. +- .gitignore covers coverage, mypy, logs, and editor directories. + +What was not done: +- (none) diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/How-to-implement-the-changes.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/How-to-implement-the-changes.md new file mode 100644 index 0000000..445965c --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/How-to-implement-the-changes.md @@ -0,0 +1,8 @@ +# How to implement the changes: + +Diffs are against `main` @ `1bbd0a6` with a dirty tree. + +1. `src/verae_mcp_timestamping_server/global_client.py` — `await asyncio.to_thread(...)` around `submit` and `verify_certificate`. +2. `src/verae_mcp_timestamping_server/ledger.py` — `os.chmod(self.path, 0o600)` after `_save`. +3. `.gitignore` — add `.coverage`, `htmlcov/`, `.mypy_cache/`, `*.log`, `.idea/`, `.vscode/`. +4. `uv run pytest`. diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/README.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/README.md new file mode 100644 index 0000000..88ccde2 --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/README.md @@ -0,0 +1,26 @@ +# Recommended changes: event loop and ledger permissions + +Review of the Verae MCP timestamping server on 2026-09-21. Pins are in git-versions.md. + +## Install + +```sh +cd /Users/marchon/research/modelcontextprotocol/verae-mcp-timestamping-server +uv sync --group dev +``` + +## Usage + +```sh +less recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change.md +less recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change-To.md +less recommended-changes/2026-09-21-event-loop-and-ledger-perms/How-to-implement-the-changes.md +``` + +## Contributing + +Questions: this research workspace. PRs welcome. + +## License + +UNLICENSED © George Lambert diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change-To.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change-To.md new file mode 100644 index 0000000..f5eddef --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change-To.md @@ -0,0 +1,7 @@ +# What to Change To: + +| Component | Target behavior | Acceptance criteria | Constraints | +| --- | --- | --- | --- | +| Global HTTP calls | `submit` and `verify_certificate` run in a worker thread | `uv run pytest` still passes, including `test_global_client.py` | Same JSON bodies and receipt fields | +| Local receipt file | Mode `0600` after every save | New file is not group- or world-readable | Do not change the JSON shape | +| `.gitignore` | Ignore coverage, mypy, logs, IDE dirs | `git status` does not list those artifacts | Keep `debug-trace/` tracked; those HTML traces are the stamp record | diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change.md new file mode 100644 index 0000000..e1a3cdf --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/What-to-Change.md @@ -0,0 +1,16 @@ +# What to Change: + +Reviewed against `main` @ `1bbd0a6` (dirty working tree). `uv run pytest` — 14 passed. + +## What is working + +- Tools `stamp`, `verify`, and `hash_report` accept a SHA-256 hex digest and refuse to take file bytes. +- `scope=global` posts to `https://api.veraetime.net/api/timestamp` with `wait=true`. +- Local receipts persist in `~/.verae/mcp-timestamp-local.json`. +- Job-complete traffic is not on JetStream. NATS use here is request/reply on `verae.ts.request`, `verae.ts.verify`, and `verae.hash.report` only when a NatsLedger is constructed. The default process uses the file ledger plus HTTPS. + +| Component | Problem | Evidence | Severity | Type | +| --- | --- | --- | --- | --- | +| `GlobalLedger.stamp` / `verify` | Blocking `urllib` and `time.sleep` run on the asyncio loop while other MCP tools wait | `global_client.py` `submit` calls `urlopen` and `time.sleep(0.25)` from `async def stamp` | high | design flaw | +| `FileLedger._save` | Receipt file is created with the process umask, often world-readable | `ledger.py` `write_text` with no `chmod` | high | missing gate | +| `.gitignore` | Coverage, mypy, logs, and editor dirs are not ignored | `.gitignore` listed venv and pytest only | low | missing gate | diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.json b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.json new file mode 100644 index 0000000..2b7aa1c --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.json @@ -0,0 +1,12 @@ +[ + { + "path": "/Users/marchon/research/modelcontextprotocol/verae-mcp-timestamping-server", + "remote": "", + "branch": "main", + "head": "1bbd0a68643e51c21bb8975e01b7f5cfc0df3a14", + "short": "1bbd0a6", + "dirty": true, + "describe": "1bbd0a6-dirty", + "porcelain": "M .gitignore\n M src/verae_mcp_timestamping_server/global_client.py\n M src/verae_mcp_timestamping_server/ledger.py" + } +] diff --git a/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.md b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.md new file mode 100644 index 0000000..18c2a0a --- /dev/null +++ b/recommended-changes/2026-09-21-event-loop-and-ledger-perms/git-versions.md @@ -0,0 +1,6 @@ +# git-versions + +Pins at pack creation. Dirty means uncommitted files existed. + +- `/Users/marchon/research/modelcontextprotocol/verae-mcp-timestamping-server` `main` `1bbd0a6` (dirty) `1bbd0a6-dirty` + - HEAD: `1bbd0a68643e51c21bb8975e01b7f5cfc0df3a14` diff --git a/src/verae_mcp_timestamping_server/global_client.py b/src/verae_mcp_timestamping_server/global_client.py index 6abdbe4..42becab 100644 --- a/src/verae_mcp_timestamping_server/global_client.py +++ b/src/verae_mcp_timestamping_server/global_client.py @@ -6,6 +6,7 @@ The global server is the source of truth for scope=global. from __future__ import annotations +import asyncio import json import os import time @@ -225,11 +226,12 @@ class GlobalLedger: ) -> list[Receipt]: del org_id digest = validate_sha256(sha256) - return [self.client.submit(digest, cube_id=cube_id)] + receipt = await asyncio.to_thread(self.client.submit, digest, cube_id) + return [receipt] async def verify(self, receipt_id: str, want_scope: str = "") -> VerifyResult: del want_scope - return self.client.verify_certificate(receipt_id) + return await asyncio.to_thread(self.client.verify_certificate, receipt_id) async def hash_report( self, sha256: str, org_id: str = "", want_scope: str = "" diff --git a/src/verae_mcp_timestamping_server/ledger.py b/src/verae_mcp_timestamping_server/ledger.py index 554b771..3fe9fe4 100644 --- a/src/verae_mcp_timestamping_server/ledger.py +++ b/src/verae_mcp_timestamping_server/ledger.py @@ -184,6 +184,7 @@ class FileLedger(MemoryLedger): self.path.parent.mkdir(parents=True, exist_ok=True) payload = [self._by_id[rid].model_dump() for rid in self._order] self.path.write_text(json.dumps(payload, indent=2) + "\n") + os.chmod(self.path, 0o600) async def stamp( self,