From 8a67f91d8b12b95443033ede03f3a01f74f3197c Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Wed, 22 Jul 2026 23:01:48 -0700 Subject: [PATCH] fix(server): widen the boot epoch to 64 bits; docstring precision (#881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit token_hex(4) left the epoch equality check — the only thing between a prior-boot cursor and a silent replay_ok-empty alias — at 2^-32 per same-node restart-pair; 64 bits puts a fleet-lifetime of restarts engineered far below threshold (review round 4, classified design-margin). Docstring rounds from the same pass: the resume contract now notes reason=boot_epoch also covers the same-epoch empty-ring fail-safe (not exclusively foreign epochs), and the collector ruling says precisely that the staleness CHECK and envelope can never fire there — the epoch-tagged ids are on the wire, just never read. --- tests/test_global_sse_boot_epoch.py | 2 +- turnstone/console/collector.py | 5 +++-- turnstone/server.py | 14 +++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/test_global_sse_boot_epoch.py b/tests/test_global_sse_boot_epoch.py index 6e3e184d..876f6e48 100644 --- a/tests/test_global_sse_boot_epoch.py +++ b/tests/test_global_sse_boot_epoch.py @@ -398,7 +398,7 @@ def test_epoch_is_hex_and_dashless_by_construction() -> None: import secrets for _ in range(64): - assert "-" not in secrets.token_hex(4) + assert "-" not in secrets.token_hex(8) # And the production init uses token_hex — source-level pin. import inspect diff --git a/turnstone/console/collector.py b/turnstone/console/collector.py index 1720d337..d190967e 100644 --- a/turnstone/console/collector.py +++ b/turnstone/console/collector.py @@ -284,8 +284,9 @@ class ClusterCollector: ``node_snapshot`` and the collector rebuilds wholesale. That is this consumer's recovery model (idempotent state-of-world, not append-only history), it side-steps cursor staleness across node - restarts entirely, and it means the epoch-tagged ids and the - ``replay_truncated`` envelope on this stream can never fire here. + restarts entirely, and it means the epoch staleness check and + the ``replay_truncated`` envelope can never fire here — the + epoch-tagged ids themselves are on the wire, just never read. If a cursor is ever adopted, present the SSE ``id:`` VERBATIM (opaque ``"{boot_epoch}-{counter}"`` — never parse it) and handle ``replay_truncated`` explicitly; today an unknown event type diff --git a/turnstone/server.py b/turnstone/server.py index 1bb51baf..f2b418e7 100644 --- a/turnstone/server.py +++ b/turnstone/server.py @@ -1029,7 +1029,10 @@ async def global_events_sse(request: Request) -> Response: from any other epoch — a prior boot, another node, a pre-#881 bare-int client — gets ``replay_truncated`` with ``reason="boot_epoch"`` followed by a fresh ``node_snapshot``, since - the events it missed died with the process that minted it. Clients + the events it missed died with the process that minted it. + ``boot_epoch`` is not exclusively a foreign-epoch signal: a + same-epoch cursor over an EMPTY ring (impossible from our own ids — + forged or a bug) also draws it via the fail-safe branch below. Clients treat the cursor as an opaque string; only this handler parses it. """ # -- Service-scope gate --------------------------------------------------- @@ -5172,8 +5175,13 @@ def create_app( # differs from the live process is stale by construction and draws # the ``replay_truncated`` + node_snapshot recovery floor in # :func:`global_events_sse`. Hex nonce (never contains ``-``), so - # ``partition("-")`` splits the id unambiguously. - app.state.global_boot_epoch = secrets.token_hex(4) + # ``partition("-")`` splits the id unambiguously. 64 bits: the + # equality check is the ONLY thing standing between a prior-boot + # cursor and a silent ``replay_ok``-empty alias, and the collision + # event is per same-node restart-pair — 2^-64 keeps a + # fleet-lifetime of restarts engineered far below threshold where + # 32 bits left it merely unlikely (review round 4). + app.state.global_boot_epoch = secrets.token_hex(8) app.state.skip_permissions = skip_permissions app.state.jwt_secret = jwt_secret app.state.auth_storage = auth_storage