feat(daemon): POST /sync endpoint for dashboard Sync Now button#196
Conversation
Adds POST /sync to the local daemon so the cloud dashboard's Sync Now button (SyncStatus.tsx) works. The dashboard POSTs to http://127.0.0.1:8765/sync but the daemon previously only had /health and per-feature endpoints, so the button always failed with 'Local Gradata daemon not running' banner. The new handler mirrors sync_cron.py exactly: - Reads watermark from $BRAIN_DIR/.sync_cron_watermark - SELECT * FROM events WHERE id > watermark ORDER BY id LIMIT 500 - Builds (events, corrections) payload with same severity normalization and dedup-by-(session, description) as the cron pusher - POSTs to GRADATA_CLOUD_API_URL (default api.gradata.ai/api/v1/sync) with Bearer auth - API key resolution: --api-key flag > GRADATA_API_KEY env > ~/.gradata/key - Atomic watermark write on success - Returns {status, pushed, last_sync_at} on success - 502 on cloud HTTP/network failure (watermark NOT advanced) - 500 on unexpected exceptions (logged with exc_info) CORS: dashboard at https://app.gradata.ai is cross-origin to 127.0.0.1. Added Access-Control-Allow-Origin to all responses and an OPTIONS preflight handler returning 204 with the allow-methods/-headers. Tests: tests/test_daemon_sync.py covers happy path, no-new-events, HTTP error, network error, missing API key, OPTIONS preflight, CORS header on POST, plus direct unit tests for payload dedup and key resolution priority. Layering check: daemon.py stays in Layer 2. New helpers use stdlib (sqlite3, urllib, json, os, pathlib) — no Layer 0 -> 2 imports introduced.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 Walkthrough
WalkthroughAdds a cloud synchronization endpoint ( ChangesCloud Sync Endpoint
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.20.0)OpenGrep fatal error (exit code 2): �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Gradata/src/gradata/daemon.py`:
- Around line 299-302: Remove the unnecessary credential header from the CORS
helper: in the _write_cors_headers method remove the call that sets
"Access-Control-Allow-Credentials" so only "Access-Control-Allow-Origin" (and
any other non-credential CORS headers) are sent; this prevents sending
Access-Control-Allow-Credentials: true alongside Access-Control-Allow-Origin:
"*" which violates the CORS spec for the /sync endpoint that uses server-side
API key auth.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 058d3c77-645d-4ccd-947c-224486793824
📒 Files selected for processing (2)
Gradata/src/gradata/daemon.pyGradata/tests/test_daemon_sync.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: pytest windows-latest / py3.11
- GitHub Check: pytest macos-latest / py3.11
- GitHub Check: pytest ubuntu-latest / py3.12
- GitHub Check: pytest ubuntu-latest / py3.11
- GitHub Check: pytest macos-latest / py3.12
- GitHub Check: pytest windows-latest / py3.12
- GitHub Check: pytest (py3.12)
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/src/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/src/**/*.py: Prefersentence-transformersfor local embeddings,google-genaifor Gemini embeddings,cryptographyfor AES-GCM encrypted system.db,bm25sfor BM25 rule ranking, andmem0aifor external memory adapters — guard all optional dependency imports withtry / except ImportErrorat the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bareexcept: pass— use typed exceptions or at minimumlogger.warning(...)withexc_info=Trueto avoid silent failure in a memory product
Never import from out-of-scope sibling directories../Sprites/or../Hausgem/withingradata/*code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to../Sprites/,../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from insidegradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes
Files:
Gradata/src/gradata/daemon.py
Gradata/tests/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/tests/**/*.py: SetBRAIN_DIRenvironment variable viatmp_pathin conftest.py for test isolation — ensure_paths.pymodule cache refreshes when callingBrain.init()directly inside tests
Add unit tests intests/test_*.pyfor every CI push without LLM calls (deterministic); mark integration tests with@pytest.mark.integrationand skip them by default (they hit real LLM APIs)
Files:
Gradata/tests/test_daemon_sync.py
🔇 Additional comments (21)
Gradata/src/gradata/daemon.py (10)
38-39: LGTM!
109-116: LGTM!
118-133: LGTM!
136-153: LGTM!
156-222: LGTM!
253-264: LGTM!
278-278: LGTM!Also applies to: 309-309
777-905: LGTM!
925-941: LGTM!
1236-1253: LGTM!Gradata/tests/test_daemon_sync.py (11)
1-30: LGTM!
35-109: LGTM!
112-125: LGTM!
131-168: LGTM!
171-204: LGTM!
210-231: LGTM!
234-247: LGTM!
250-291: LGTM!
297-321: LGTM!
327-382: LGTM!
385-406: LGTM!
| def _write_cors_headers(self) -> None: | ||
| """Write CORS response headers (called between send_response and end_headers).""" | ||
| self.send_header("Access-Control-Allow-Origin", "*") | ||
| self.send_header("Access-Control-Allow-Credentials", "true") |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Remove unnecessary Access-Control-Allow-Credentials header.
Since the /sync endpoint authenticates via a server-side API key (not browser credentials), Access-Control-Allow-Credentials: true is unnecessary. More importantly, per CORS spec, when this header is set to true, the Access-Control-Allow-Origin header must specify an explicit origin—not *. Browsers will reject credentialed requests if both are present as shown.
Since no browser credentials are used, simply remove the credentials header:
Proposed fix
def _write_cors_headers(self) -> None:
"""Write CORS response headers (called between send_response and end_headers)."""
self.send_header("Access-Control-Allow-Origin", "*")
- self.send_header("Access-Control-Allow-Credentials", "true")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _write_cors_headers(self) -> None: | |
| """Write CORS response headers (called between send_response and end_headers).""" | |
| self.send_header("Access-Control-Allow-Origin", "*") | |
| self.send_header("Access-Control-Allow-Credentials", "true") | |
| def _write_cors_headers(self) -> None: | |
| """Write CORS response headers (called between send_response and end_headers).""" | |
| self.send_header("Access-Control-Allow-Origin", "*") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Gradata/src/gradata/daemon.py` around lines 299 - 302, Remove the unnecessary
credential header from the CORS helper: in the _write_cors_headers method remove
the call that sets "Access-Control-Allow-Credentials" so only
"Access-Control-Allow-Origin" (and any other non-credential CORS headers) are
sent; this prevents sending Access-Control-Allow-Credentials: true alongside
Access-Control-Allow-Origin: "*" which violates the CORS spec for the /sync
endpoint that uses server-side API key auth.
Adds POST /sync to the local daemon so the cloud dashboard's Sync Now button works.
Bug
Dashboard SyncStatus.tsx POSTs to http://127.0.0.1:8765/sync but the daemon only had /health and per-feature endpoints. Button always failed with 'Local Gradata daemon not running' banner.
Change
Test plan
tests/test_daemon_sync.py: green.
Manual: daemon running, curl -X POST http://127.0.0.1:8765/sync returns pushed count.
Layering
No Layer 0 -> 2 imports. daemon.py already uses _brain_lock, sqlite3 stdlib, urllib.request stdlib.