diff --git a/src/verae_mcp_timestamping_server/global_client.py b/src/verae_mcp_timestamping_server/global_client.py index 89a3d1c..7a43f31 100644 --- a/src/verae_mcp_timestamping_server/global_client.py +++ b/src/verae_mcp_timestamping_server/global_client.py @@ -8,6 +8,7 @@ from __future__ import annotations import json import os +import time import urllib.error import urllib.request from pathlib import Path @@ -42,7 +43,7 @@ class UrllibHttp: def request( self, method: str, url: str, payload: dict[str, Any], headers: dict[str, str] ) -> _UrllibResp: - data = json.dumps(payload).encode() + data = None if method == "GET" else json.dumps(payload).encode() req = urllib.request.Request(url, data=data, method=method) for key, value in headers.items(): req.add_header(key, value) @@ -106,7 +107,7 @@ class GlobalClient: res = self.http.request( "POST", url, - payload={"hash": digest, "sync": True}, + payload={"data": digest, "hashAlg": "SHA256"}, headers=self._headers(), ) if res.status_code >= 300: @@ -115,13 +116,37 @@ class GlobalClient: job_id = parsed.get("jobId") or parsed.get("blockHash") or "" if not job_id: raise ValueError("global timestamp response missing jobId") + created = str(parsed.get("timestamp") or "") + merkle = str(parsed.get("blockHash") or "") + receipt_id = str(job_id) + status_url = f"{self.base_url}/api/status/{job_id}" + for _ in range(20): + st = self.http.request("GET", status_url, payload={}, headers=self._headers()) + if st.status_code >= 300: + break + body = st.json() if hasattr(st, "json") else {} + if not isinstance(body, dict): + break + state = str(body.get("status") or "") + if state == "failed": + raise ValueError(str(body.get("error") or "global timestamp job failed")) + if state == "completed": + receipt_id = str(body.get("result") or body.get("id") or job_id) + meta = body.get("metadata") or {} + created = str(meta.get("timestamp") or body.get("completedAt") or created) + merkle = str(meta.get("blockIndex") or meta.get("blockHash") or merkle) + break + if state in ("pending", "running", ""): + time.sleep(0.25) + continue + break return Receipt( - receipt_id=str(job_id), + receipt_id=receipt_id, hash=digest, scope="global", cube_id=cube_id, - created_at=str(parsed.get("timestamp") or ""), - merkle=str(parsed.get("blockHash") or ""), + created_at=created, + merkle=merkle, ) def verify_certificate(self, receipt_id: str) -> VerifyResult: diff --git a/tests/test_global_client.py b/tests/test_global_client.py index a46a87a..b1188b1 100644 --- a/tests/test_global_client.py +++ b/tests/test_global_client.py @@ -29,7 +29,7 @@ class FakeHttp: 200, { "jobId": "job-1", - "hash": payload["hash"], + "hash": payload.get("data") or payload.get("hash"), "timestamp": "2026-08-15T00:00:00Z", "blockHash": "0xdead", }, @@ -38,6 +38,16 @@ class FakeHttp: if payload.get("certificate") == "job-1": return _Resp(200, {"valid": True, "hash": SHA256_EMPTY}) return _Resp(200, {"valid": False}) + if "/api/status/" in url: + return _Resp( + 200, + { + "id": "job-1", + "status": "completed", + "result": "job-1", + "metadata": {"timestamp": "2026-08-15T00:00:00Z", "blockHash": "0xdead"}, + }, + ) return _Resp(404, {"error": "nope"}) @@ -49,7 +59,7 @@ async def test_global_stamp_posts_hash_sync_and_maps_receipt(): method, url, payload, headers = http.calls[0] assert method == "POST" assert url == "https://api.veraetime.net/api/timestamp" - assert payload == {"hash": SHA256_EMPTY, "sync": True} + assert payload == {"data": SHA256_EMPTY, "hashAlg": "SHA256"} assert headers["Authorization"] == "Bearer secret" assert rec.scope == "global" assert rec.receipt_id == "job-1" @@ -84,7 +94,7 @@ async def test_routing_sends_only_global_scope_to_central(): [g] = await routed.stamp(SHA256_EMPTY, scope="global", cube_id="cube-1") assert g.scope == "global" assert g.receipt_id == "job-1" - assert len(http.calls) == 1 + assert http.calls[0][1].endswith("/api/timestamp") cached = await routed.hash_report(SHA256_EMPTY, want_scope="global") assert cached[0].receipt_id == "job-1"