Skip to content

Commit 0ea5fac

Browse files
andinuxclaude
andcommitted
feat(network): support cloudsync failures.{apply,check} response shape
Replaces the old single `lastFailure` extraction with per-stage `failures.apply` / `failures.check`. `send.lastFailure` is now sourced from `failures.apply`; a new `receive.lastFailure` surfaces `failures.check`. Per-function scoping is strict: send_changes is send/apply-scoped, check_changes is check-scoped, sync reports both. Also handles the new dual-shape `/check` response: HTTP 200 with `{url}` (artifact) vs HTTP 202 with the SyncStatusResponse snapshot (no artifact yet). Previously the 202 path errored with "missing 'url'" against the new server. Bumps CLOUDSYNC_VERSION to 1.0.18 and updates API.md / CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c8897a2 commit 0ea5fac

4 files changed

Lines changed: 149 additions & 68 deletions

File tree

API.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ The sync functions follow a consistent error-handling contract:
490490
| **Endpoint/network errors** (server unreachable, auth failure, bad URL) | SQL error — the function could not execute. |
491491
| **Apply errors** (`cloudsync_payload_apply` failures — unknown schema hash, invalid checksum, decompression error) | Structured JSON — a `receive.error` string field is included in the response. |
492492
| **Server-reported apply job failures** (the server processed the request but its own apply job failed) | Structured JSON — a `send.lastFailure` object is included in the response. |
493+
| **Server-reported check job failures** (the server failed to encode a changeset for the client) | Structured JSON — a `receive.lastFailure` object is included in the response. |
493494

494495
This means: if you get JSON back, the server was reachable and the network protocol ran. If you get a SQL error, connectivity or configuration is broken.
495496

@@ -510,7 +511,7 @@ This means: if you get JSON back, the server was reachable and the network proto
510511
- `send.status`: The current sync state — `"synced"` (all changes confirmed), `"syncing"` (changes sent but not yet confirmed), `"out-of-sync"` (local changes pending or gaps detected), or `"error"`.
511512
- `send.localVersion`: The latest local database version.
512513
- `send.serverVersion`: The latest version confirmed by the server.
513-
- `send.lastFailure` (optional): Present only when the server reports a failed apply job. The object is forwarded verbatim from the server and typically includes `jobId`, `code`, `message`, `retryable`, and `failedAt`. It is emitted regardless of `status` so callers can detect server-side failures during `"syncing"` or even after the state has nominally recovered.
514+
- `send.lastFailure` (optional): Present only when the server reports a failed apply job. Forwarded verbatim from the server's `failures.apply` and typically includes `jobId`, `code`, `stage`, `message`, `retryable`, and `failedAt`. It is emitted regardless of `status` so callers can detect server-side failures during `"syncing"` or even after the state has nominally recovered. This function is **send/apply-scoped**: server-reported check-job failures (`failures.check`) are not surfaced here — see [`cloudsync_network_check_changes()`](#cloudsync_network_check_changes) and [`cloudsync_network_sync()`](#cloudsync_network_sync).
514515

515516
**Example:**
516517

@@ -519,7 +520,7 @@ SELECT cloudsync_network_send_changes();
519520
-- '{"send":{"status":"synced","localVersion":5,"serverVersion":5}}'
520521

521522
-- With a server-reported failure (e.g. unknown schema hash on the server side):
522-
-- '{"send":{"status":"out-of-sync","localVersion":1,"serverVersion":0,"lastFailure":{"jobId":44961,"code":"internal_error","message":"cloudsync operation failed: Cannot apply the received payload because the schema hash is unknown 4288148391734624266.","retryable":true,"failedAt":"2026-04-15T22:21:09.018606Z"}}}'
523+
-- '{"send":{"status":"out-of-sync","localVersion":1,"serverVersion":0,"lastFailure":{"jobId":44961,"code":"internal_error","stage":"apply_payload","message":"cloudsync operation failed: Cannot apply the received payload because the schema hash is unknown 4288148391734624266.","retryable":true,"failedAt":"2026-04-15T22:21:09.018606Z"}}}'
523524
```
524525

525526
---
@@ -533,28 +534,32 @@ If a package of new changes is already available for the local site, the server
533534
This function is designed to be called periodically to keep the local database in sync.
534535
To force an update and wait for changes (with a timeout), use [`cloudsync_network_sync(wait_ms, max_retries)`].
535536

536-
If the network is misconfigured or the remote server is unreachable, the function raises a SQL error. If the received payload cannot be applied locally (for example because of an unknown schema hash), the error is returned as a `receive.error` field in the JSON response.
537+
If the network is misconfigured or the remote server is unreachable, the function raises a SQL error. If the received payload cannot be applied locally (for example because of an unknown schema hash), the error is returned as a `receive.error` field in the JSON response. If the server reports an unresolved failed check job (e.g. an `encode_changes` failure), that failure is forwarded as a `receive.lastFailure` object.
537538

538539
**Parameters:** None.
539540

540541
**Returns:** A JSON string with the receive result:
541542

542543
```json
543-
{"receive": {"rows": N, "tables": ["table1", "table2"], "error": "..."}}
544+
{"receive": {"rows": N, "tables": ["table1", "table2"], "error": "...", "lastFailure": {...}}}
544545
```
545546

546547
- `receive.rows`: The number of rows received and applied to the local database. `0` when the receive phase failed.
547548
- `receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied or the receive phase failed.
548-
- `receive.error` (optional): Present when `cloudsync_payload_apply` failed. Contains a human-readable error message describing why the received payload could not be applied.
549+
- `receive.error` (optional, string): Present when client-side `cloudsync_payload_apply` failed. Contains a human-readable error message describing why the received payload could not be applied.
550+
- `receive.lastFailure` (optional, object): Present only when the server reports a failed check job. Forwarded verbatim from the server's `failures.check` and typically includes `jobId`, `dbVersion`, `seq`, `code`, `stage`, `message`, `retryable`, and `failedAt`. Distinct from `receive.error`: `receive.error` describes a client-side apply failure (string), while `receive.lastFailure` describes a server-side check-job failure (object). Both can coexist in the same response. This function is **check-scoped**: server-reported apply-job failures (`failures.apply`) are not surfaced here — see [`cloudsync_network_send_changes()`](#cloudsync_network_send_changes) and [`cloudsync_network_sync()`](#cloudsync_network_sync).
549551

550552
**Example:**
551553

552554
```sql
553555
SELECT cloudsync_network_check_changes();
554556
-- '{"receive":{"rows":3,"tables":["tasks"]}}'
555557

556-
-- With an apply error:
558+
-- With a client-side apply error:
557559
-- '{"receive":{"rows":0,"tables":[],"error":"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."}}'
560+
561+
-- With a server-reported check-job failure:
562+
-- '{"receive":{"rows":0,"tables":[],"lastFailure":{"jobId":456,"dbVersion":15,"seq":1,"code":"tenant_unreachable","stage":"encode_changes","message":"tenant check failed","retryable":true,"failedAt":"2026-04-24T10:22:00Z"}}}'
558563
```
559564

560565
---
@@ -576,17 +581,18 @@ SELECT cloudsync_network_check_changes();
576581
```json
577582
{
578583
"send": {"status": "synced|syncing|out-of-sync|error", "localVersion": N, "serverVersion": N, "lastFailure": {...}},
579-
"receive": {"rows": N, "tables": ["table1", "table2"], "error": "..."}
584+
"receive": {"rows": N, "tables": ["table1", "table2"], "error": "...", "lastFailure": {...}}
580585
}
581586
```
582587

583588
- `send.status`: The current sync state — `"synced"`, `"syncing"`, `"out-of-sync"`, or `"error"`.
584589
- `send.localVersion`: The latest local database version.
585590
- `send.serverVersion`: The latest version confirmed by the server.
586-
- `send.lastFailure` (optional): Same semantics as in [`cloudsync_network_send_changes()`](#cloudsync_network_send_changes) — forwarded verbatim from the server whenever a failed apply job is reported, regardless of `status`.
591+
- `send.lastFailure` (optional): Same semantics as in [`cloudsync_network_send_changes()`](#cloudsync_network_send_changes) — forwarded verbatim from the server's `failures.apply` whenever a failed apply job is reported, regardless of `status`.
587592
- `receive.rows`: The number of rows received and applied during the check phase. `0` when the receive phase failed.
588593
- `receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied or the receive phase failed.
589-
- `receive.error` (optional): Present when `cloudsync_payload_apply` failed (for example `"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."`). The send result is always preserved so the caller can tell that local changes reached the server even when applying incoming changes failed. The retry loop breaks immediately on apply errors, since failures like schema-hash mismatches do not heal across retries. Endpoint/network errors during the receive phase raise a SQL error instead.
594+
- `receive.error` (optional, string): Present when client-side `cloudsync_payload_apply` failed (for example `"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."`). The send result is always preserved so the caller can tell that local changes reached the server even when applying incoming changes failed. The retry loop breaks immediately on apply errors, since failures like schema-hash mismatches do not heal across retries. Endpoint/network errors during the receive phase raise a SQL error instead.
595+
- `receive.lastFailure` (optional, object): Same semantics as in [`cloudsync_network_check_changes()`](#cloudsync_network_check_changes) — forwarded verbatim from the server's `failures.check` whenever a failed check job is reported. Distinct from `receive.error`. `cloudsync_network_sync()` reports both `send.lastFailure` and `receive.lastFailure` when present.
590596

591597
**Example:**
592598

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## [1.0.18] - 2026-04-27
8+
9+
### Changed
10+
11+
- **`cloudsync_network_send_changes()` and `cloudsync_network_sync()`**: `send.lastFailure` is now sourced from the server's per-stage `failures.apply` object. The wire-format SQL field (`send.lastFailure`) is unchanged in shape — it is still forwarded verbatim from the server.
12+
- **`cloudsync_network_check_changes()`**: now correctly handles the `/check` endpoint's status snapshot response (returned when no artifact is yet available). Previously this path errored with `missing 'url' in check response`; the function now treats it as "no rows yet, not an error" and surfaces any server-reported check failure via the new `receive.lastFailure` field.
13+
14+
### Added
15+
16+
- **`receive.lastFailure`** JSON field on `cloudsync_network_check_changes()` and `cloudsync_network_sync()`. Forwarded verbatim from the server's `failures.check` (e.g. `encode_changes` job failures), and emitted alongside but distinct from `receive.error` (which remains a string for client-side `cloudsync_payload_apply` failures). Per-function scoping is strict: `cloudsync_network_send_changes()` is send/apply-scoped (only `send.lastFailure`); `cloudsync_network_check_changes()` is check-scoped (only `receive.lastFailure`); `cloudsync_network_sync()` reports both.
17+
718
## [1.0.17] - 2026-04-24
819

920
### Fixed

src/cloudsync.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
extern "C" {
1919
#endif
2020

21-
#define CLOUDSYNC_VERSION "1.0.17"
21+
#define CLOUDSYNC_VERSION "1.0.18"
2222
#define CLOUDSYNC_MAX_TABLENAME_LEN 512
2323

2424
#define CLOUDSYNC_VALUE_NOTSET -1

0 commit comments

Comments
 (0)