From 8ce46a73aa20d8f41282582a8e1a79ce3e5e285d Mon Sep 17 00:00:00 2001 From: data-engineer Date: Sat, 16 May 2026 17:21:31 -0700 Subject: [PATCH] feat(sync): retire hook-based cloud sync now that write-through is live (day 4 of #194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brain.correct() write-through (PR #200) is now the default cloud sync path. The session_close hook's legacy cloud_sync_tick was double-writing every correction. Default behavior: session_close skips cloud_sync_tick. Lesson graduation, pipeline, tree consolidation, lesson_applications resolution all still run as before — only the cloud sync portion is gated. Opt-out: GRADATA_DISABLE_WRITE_THROUGH=1 restores the legacy hook-based cloud sync path AND disables write-through. The two paths are mutually exclusive: only one ever runs. Untouched: - daemon /sync (dashboard 'Sync Now' button) - /api/v1/sync (bulk endpoint, used by Brain.close() drain) - /home/olive/.gradata/sync_cron.py (safety-net cron, keep for 7-day soak) Follow-up: after 7 days of clean write-through telemetry in cloud, the cron and legacy /api/v1/sync code paths can be removed in a separate PR. --- Gradata/src/gradata/hooks/session_close.py | 12 ++++++ .../test_session_close_write_through_gate.py | 42 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 Gradata/tests/test_session_close_write_through_gate.py diff --git a/Gradata/src/gradata/hooks/session_close.py b/Gradata/src/gradata/hooks/session_close.py index 8e663326..cf0e4970 100644 --- a/Gradata/src/gradata/hooks/session_close.py +++ b/Gradata/src/gradata/hooks/session_close.py @@ -278,9 +278,21 @@ def _run_cloud_sync(brain_dir: str, data: dict) -> None: Claude Code never calls ``brain.end_session()`` directly, so ``_cloud_sync_session`` never fired from IDE sessions before this hook path existed. Gated on GRADATA_API_KEY — no key, no sync, no network. + + As of #194 day 4, Brain.correct() write-through is the default cloud + sync path. The session_close hook tick is redundant when write-through + is active and was double-writing every correction. Set + GRADATA_DISABLE_WRITE_THROUGH=1 to fall back to this legacy path. """ if not os.environ.get("GRADATA_API_KEY"): return + # Default behavior: write-through covers it. Skip the legacy tick. + if os.environ.get("GRADATA_DISABLE_WRITE_THROUGH") != "1": + _log.info( + "write-through enabled, skipping legacy cloud sync from session_close " + "(set GRADATA_DISABLE_WRITE_THROUGH=1 to restore legacy behavior)" + ) + return try: from gradata._core import cloud_sync_tick diff --git a/Gradata/tests/test_session_close_write_through_gate.py b/Gradata/tests/test_session_close_write_through_gate.py new file mode 100644 index 00000000..9add19d9 --- /dev/null +++ b/Gradata/tests/test_session_close_write_through_gate.py @@ -0,0 +1,42 @@ +"""Test the GRADATA_DISABLE_WRITE_THROUGH gate on session_close cloud sync. + +#194 day 4: Brain.correct() write-through is the default cloud sync path. +The session_close hook's legacy cloud_sync_tick is now skipped by default +to avoid double-writes. Only runs when GRADATA_DISABLE_WRITE_THROUGH=1. +""" + +from __future__ import annotations + +from unittest.mock import patch + +from gradata.hooks import session_close + + +def test_run_cloud_sync_skipped_when_write_through_default(monkeypatch): + """Default behavior: write-through covers it, session_close tick is skipped.""" + monkeypatch.setenv("GRADATA_API_KEY", "gd_live_test") + monkeypatch.delenv("GRADATA_DISABLE_WRITE_THROUGH", raising=False) + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + ( + mock_tick.assert_not_called(), + ("session_close must NOT call cloud_sync_tick when write-through is on"), + ) + + +def test_run_cloud_sync_invoked_when_write_through_disabled(monkeypatch): + """Opt-out via GRADATA_DISABLE_WRITE_THROUGH=1: legacy path runs.""" + monkeypatch.setenv("GRADATA_API_KEY", "gd_live_test") + monkeypatch.setenv("GRADATA_DISABLE_WRITE_THROUGH", "1") + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + mock_tick.assert_called_once_with("/tmp/fake-brain", 1) + + +def test_run_cloud_sync_skipped_when_no_api_key(monkeypatch): + """No API key: never call cloud regardless of write-through state.""" + monkeypatch.delenv("GRADATA_API_KEY", raising=False) + monkeypatch.setenv("GRADATA_DISABLE_WRITE_THROUGH", "1") + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + mock_tick.assert_not_called()