memwal 0.1.7.dev0__tar.gz → 0.1.7.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/CHANGELOG.md +6 -0
  2. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/PKG-INFO +1 -1
  3. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/__init__.py +3 -1
  4. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/client.py +19 -0
  5. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/pyproject.toml +1 -1
  6. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_client.py +25 -1
  7. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/.gitignore +0 -0
  8. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/README.md +0 -0
  9. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/examples/.env.example +0 -0
  10. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/examples/.gitignore +0 -0
  11. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/examples/async_remember_demo.py +0 -0
  12. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/examples/interactive_demo.py +0 -0
  13. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/examples/verify_credentials.py +0 -0
  14. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/compatibility.py +0 -0
  15. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/middleware.py +0 -0
  16. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/mock.py +0 -0
  17. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/types.py +0 -0
  18. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/memwal/utils.py +0 -0
  19. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
  20. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/run_tests.py +0 -0
  21. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/__init__.py +0 -0
  22. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_auth_rejected_message.py +0 -0
  23. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_env_presets.py +0 -0
  24. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_integration.py +0 -0
  25. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_middleware.py +0 -0
  26. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_mock.py +0 -0
  27. {memwal-0.1.7.dev0 → memwal-0.1.7.dev1}/tests/test_signing.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # memwal
2
2
 
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - Added `MemWalClockDriftError`, raised when the relayer rejects a request because the signed timestamp is outside its accepted clock-drift window (`401` + `x-auth-error: ERR_TIMESTAMP_OUT_OF_BOUNDS`). Surfaces an actionable "synchronize the client clock" message instead of an opaque `401`. Subclasses `MemWalError`, so existing `except MemWalError` handlers still catch it.
8
+
3
9
  ## 0.1.7
4
10
 
5
11
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memwal
3
- Version: 0.1.7.dev0
3
+ Version: 0.1.7.dev1
4
4
  Summary: Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing
5
5
  Project-URL: Homepage, https://memory.walrus.xyz
6
6
  Project-URL: Documentation, https://memory.walrus.xyz
@@ -24,6 +24,7 @@ Quick start::
24
24
 
25
25
  from .client import (
26
26
  MemWal,
27
+ MemWalClockDriftError,
27
28
  MemWalCompatibilityError,
28
29
  MemWalError,
29
30
  MemWalRememberJobFailed,
@@ -78,6 +79,7 @@ __all__ = [
78
79
  "MemWalMockSeed",
79
80
  "MemWalError",
80
81
  "MemWalCompatibilityError",
82
+ "MemWalClockDriftError",
81
83
  "MemWalRememberJobFailed",
82
84
  "MemWalRememberJobNotFound",
83
85
  "MemWalRememberJobTimeout",
@@ -120,4 +122,4 @@ __all__ = [
120
122
  "RecallManualResult",
121
123
  ]
122
124
 
123
- __version__ = "0.1.7.dev0"
125
+ __version__ = "0.1.7.dev1"
@@ -1189,6 +1189,16 @@ class MemWal:
1189
1189
  f"(HTTP 426 Upgrade Required). Relayer response: "
1190
1190
  f"{err_text[:300] or 'upgrade required'}"
1191
1191
  )
1192
+ # A stale/future-dated signature is rejected with 401 + a machine-
1193
+ # readable reason header. Surface it as an actionable clock-drift
1194
+ # error rather than an opaque 401 so the caller can fix node time.
1195
+ if response.headers.get("x-auth-error") == "ERR_TIMESTAMP_OUT_OF_BOUNDS":
1196
+ raise MemWalClockDriftError(
1197
+ "Request rejected: signed timestamp is outside the relayer's "
1198
+ "accepted clock-drift window. Synchronize this client's clock "
1199
+ "(NTP); if the deployment needs a wider tolerance, raise "
1200
+ "AUTH_MAX_CLOCK_DRIFT_SECS on the relayer."
1201
+ )
1192
1202
  raise _HttpStatusError(
1193
1203
  status=response.status_code,
1194
1204
  body=err_text,
@@ -1209,6 +1219,15 @@ class MemWalCompatibilityError(MemWalError):
1209
1219
  pass
1210
1220
 
1211
1221
 
1222
+ class MemWalClockDriftError(MemWalError):
1223
+ """Raised when the relayer rejects a request because the signed timestamp is
1224
+ outside its accepted clock-drift window (401 + ``x-auth-error:
1225
+ ERR_TIMESTAMP_OUT_OF_BOUNDS``). Indicates the client's clock is skewed
1226
+ relative to the relayer; sync via NTP or widen the relayer's window."""
1227
+
1228
+ pass
1229
+
1230
+
1212
1231
  class _HttpStatusError(MemWalError):
1213
1232
  """Internal: raised when an HTTP response status is not in ``accepted_statuses``.
1214
1233
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memwal"
7
- version = "0.1.7.dev0"
7
+ version = "0.1.7.dev1"
8
8
  description = "Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -17,7 +17,13 @@ import nacl.signing
17
17
  import pytest
18
18
  import respx
19
19
 
20
- from memwal.client import MemWal, MemWalCompatibilityError, MemWalError, MemWalSync
20
+ from memwal.client import (
21
+ MemWal,
22
+ MemWalClockDriftError,
23
+ MemWalCompatibilityError,
24
+ MemWalError,
25
+ MemWalSync,
26
+ )
21
27
  from memwal.types import (
22
28
  RecallManualOptions,
23
29
  RecallParams,
@@ -468,6 +474,24 @@ class TestErrorHandling:
468
474
  ):
469
475
  await memwal_client.recall("test")
470
476
 
477
+ @respx.mock
478
+ async def test_clock_drift_header_raises_clock_drift_error(
479
+ self, memwal_client: MemWal
480
+ ) -> None:
481
+ """A 401 carrying x-auth-error: ERR_TIMESTAMP_OUT_OF_BOUNDS should surface
482
+ as an actionable MemWalClockDriftError, not an opaque HTTP error."""
483
+ mock_seal_session_prereqs()
484
+ respx.post(f"{_TEST_SERVER}/api/remember").mock(
485
+ return_value=httpx.Response(
486
+ 401,
487
+ headers={"x-auth-error": "ERR_TIMESTAMP_OUT_OF_BOUNDS"},
488
+ text="",
489
+ )
490
+ )
491
+
492
+ with pytest.raises(MemWalClockDriftError, match="clock-drift window"):
493
+ await memwal_client.remember("test")
494
+
471
495
  @respx.mock
472
496
  async def test_500_raises_memwal_error(self, memwal_client: MemWal) -> None:
473
497
  """Server errors should raise MemWalError."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes