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
|
*.pyc
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.env
|
.env
|
||||||
.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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
@ -225,11 +226,12 @@ class GlobalLedger:
|
||||||
) -> list[Receipt]:
|
) -> list[Receipt]:
|
||||||
del org_id
|
del org_id
|
||||||
digest = validate_sha256(sha256)
|
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:
|
async def verify(self, receipt_id: str, want_scope: str = "") -> VerifyResult:
|
||||||
del want_scope
|
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(
|
async def hash_report(
|
||||||
self, sha256: str, org_id: str = "", want_scope: str = ""
|
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)
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
payload = [self._by_id[rid].model_dump() for rid in self._order]
|
payload = [self._by_id[rid].model_dump() for rid in self._order]
|
||||||
self.path.write_text(json.dumps(payload, indent=2) + "\n")
|
self.path.write_text(json.dumps(payload, indent=2) + "\n")
|
||||||
|
os.chmod(self.path, 0o600)
|
||||||
|
|
||||||
async def stamp(
|
async def stamp(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue