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)
This commit is contained in:
parent
1bbd0a6864
commit
60bcefbca2
10 changed files with 100 additions and 2 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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`
|
||||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue