Skip to content

Commit 7785dc3

Browse files
HassamSheikhclaude
andcommitted
docs: update README and docs for patch operation and 140-test audit
- README: add patch to features table, quick start section, API reference, security badge (140 tests), installation version - architecture.md: add patch section with code example and Supabase implementation details - security_audit.md: add Category 13 (patch operation, tests 131-140), update totals to 140 tests / 13 categories - example.dart: add Pattern C for partial update via patch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0fcf5eb commit 7785dc3

File tree

4 files changed

+66
-11
lines changed

4 files changed

+66
-11
lines changed

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Built by the team at [**dynos.fit**](https://dynos.fit) to power high-concurrenc
1010
[![Pub.dev](https://img.shields.io/pub/v/dynos_sync)](https://pub.dev/packages/dynos_sync)
1111
[![Pub Points](https://img.shields.io/pub/points/dynos_sync)](https://pub.dev/packages/dynos_sync/score)
1212
[![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
13-
[![Security Audit: 130 Tests](https://img.shields.io/badge/Security_Audit-130/130_PASS-2DD4A8)](doc/security_audit.md)
13+
[![Security Audit: 140 Tests](https://img.shields.io/badge/Security_Audit-140/140_PASS-2DD4A8)](doc/security_audit.md)
1414
[![Performance: 50k+ writes/sec](https://img.shields.io/badge/Performance-50k+_writes/sec-blueviolet)](#performance)
1515

1616
---
@@ -41,6 +41,7 @@ Flutter App --> SyncEngine --> Local DB (Drift/SQLite)
4141
| **Row-Level Security** | Local RLS gate blocks writes with mismatched `user_id` |
4242
| **Exponential backoff** | Failed pushes retry with 2^n delays, capped at `maxBackoff` |
4343
| **Poison pill isolation** | Permanently failing entries are dropped after `maxRetries` |
44+
| **Partial updates (patch)** | `SyncOperation.patch` sends UPDATE instead of upsert — no NOT NULL failures |
4445
| **Batch push** | `drain()` pushes up to `batchSize` entries per cycle |
4546
| **Auth expiry handling** | `AuthExpiredException` emits `SyncAuthRequired` event, stops drain |
4647
| **Cross-user isolation** | `logout()` wipes queue, local data, and timestamps |
@@ -55,7 +56,7 @@ Flutter App --> SyncEngine --> Local DB (Drift/SQLite)
5556

5657
```yaml
5758
dependencies:
58-
dynos_sync: ^0.1.2
59+
dynos_sync: ^0.1.5
5960
```
6061
6162
---
@@ -105,7 +106,17 @@ await sync.addTable('tags', pull: false); // register only
105106
sync.removeTable('deprecated_table'); // stop syncing
106107
```
107108

108-
### 5. Logout
109+
### 5. Partial update (patch)
110+
111+
```dart
112+
// Update only specific fields — no NOT NULL failures from missing columns
113+
await sync.push('workouts', id, {
114+
'used_at': DateTime.now().toUtc().toIso8601String(),
115+
'exercises_kept': 5,
116+
}, operation: SyncOperation.patch);
117+
```
118+
119+
### 6. Logout
109120

110121
```dart
111122
await sync.logout(); // wipes queue, local data, timestamps
@@ -121,7 +132,7 @@ await sync.logout(); // wipes queue, local data, timestamps
121132
|---|---|
122133
| `write(table, id, data)` | Write locally + queue for sync |
123134
| `remove(table, id)` | Delete locally + queue deletion |
124-
| `push(table, id, data, {operation})` | Queue sync without local write (for custom DAOs) |
135+
| `push(table, id, data, {operation})` | Queue sync without local write. `operation`: `upsert` (default), `patch`, or `delete` |
125136
| `drain()` | Push all pending queue entries to remote |
126137
| `pullAll()` | Delta-pull changes from remote for all registered tables |
127138
| `syncAll()` | Full cycle: `drain()` then `pullAll()` |
@@ -217,7 +228,7 @@ Benchmarked with in-memory stores on standard hardware:
217228

218229
## Security
219230

220-
The engine passes a **130-test security audit** across 12 categories:
231+
The engine passes a **140-test security audit** across 13 categories:
221232

222233
- HIPAA & health data compliance (PHI masking, audit trail, session timeout)
223234
- Cross-user data isolation (full wipe on logout)
@@ -227,6 +238,7 @@ The engine passes a **130-test security audit** across 12 categories:
227238
- Flood resilience (100k parallel writes, drain lock)
228239
- OWASP Mobile Top 10 coverage
229240
- GDPR compliance (right to erasure, data minimization)
241+
- Patch operation safety (partial payloads, RLS, poison pill, auth expiry)
230242

231243
See [Security Audit Report](doc/security_audit.md) for details.
232244

@@ -235,7 +247,7 @@ See [Security Audit Report](doc/security_audit.md) for details.
235247
## Documentation
236248

237249
- [Architecture](doc/architecture.md) -- sync protocol, write path, pull path, security gates
238-
- [Security Audit](doc/security_audit.md) -- 130-test audit across 12 categories
250+
- [Security Audit](doc/security_audit.md) -- 140-test audit across 13 categories
239251
- [API example](example/example.dart) -- complete Drift + Supabase setup
240252
- [Security Policy](SECURITY.md) -- vulnerability reporting
241253

doc/architecture.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ Key design decisions:
5050

5151
`push(table, id, data)` queues a sync entry without writing locally. Use this when your own DAO handles the local write and you just need the remote sync.
5252

53+
### Patch (partial update)
54+
55+
`push(table, id, data, operation: SyncOperation.patch)` queues a partial update. On the remote, this sends an `UPDATE ... WHERE id = ?` instead of an upsert. This avoids NOT NULL constraint failures when you only need to update a few fields on an existing row:
56+
57+
```dart
58+
await sync.push('workouts', id, {
59+
'used_at': DateTime.now().toUtc().toIso8601String(),
60+
'exercises_kept': 5,
61+
}, operation: SyncOperation.patch);
62+
```
63+
64+
The `SupabaseRemoteStore` implements patch via `.update(data).eq('id', id)`. In batch mode, patches are sent individually since Supabase has no batch update API.
65+
5366
### remove()
5467

5568
`remove(table, id)` queues a `SyncOperation.delete` entry and deletes from the local store.

doc/security_audit.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Security Audit Report
22

3-
`dynos_sync` is subjected to a **130-test security audit** across 12 categories before every release. This document describes what is tested and why.
3+
`dynos_sync` is subjected to a **140-test security audit** across 13 categories before every release. This document describes what is tested and why.
44

55
Test file: [`test/dynos_sync_total_audit_test.dart`](../test/dynos_sync_total_audit_test.dart)
66

@@ -22,8 +22,9 @@ Test file: [`test/dynos_sync_total_audit_test.dart`](../test/dynos_sync_total_au
2222
| 10. Denial of Service & Abuse | 108--114 | 1 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
2323
| 11. OWASP Mobile Top 10 | 115--124 | Maps to OWASP M1--M10 |
2424
| 12. GDPR & Data Sovereignty | 125--130 | 2 CRITICAL, 2 HIGH, 1 MEDIUM, 1 LOW |
25+
| 13. Patch Operation | 131--140 | 4 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
2526

26-
**Total: 130 tests, 0 failures required for release.**
27+
**Total: 140 tests, 0 failures required for release.**
2728

2829
---
2930

@@ -167,14 +168,29 @@ Explicit mapping to OWASP Mobile Top 10 (2024):
167168
- **Data residency** -- `RemoteStore` endpoint is configurable per region
168169
- **Transfer logging** -- event stream provides audit trail for all sync operations
169170

171+
### 13. Patch Operation (Tests 131--140)
172+
173+
Tests that the new `SyncOperation.patch` (partial update) is safe across all vectors:
174+
175+
- **Partial payload integrity** -- engine sends exactly the provided fields, no extras injected
176+
- **Queue persistence** -- `SyncEntry` preserves the `patch` operation type through the queue
177+
- **Drain forwarding** -- drain correctly forwards patch operations to `RemoteStore`
178+
- **Mixed batch** -- upsert + patch + delete all processed correctly in a single drain cycle
179+
- **Payload size limits** -- `maxPayloadBytes` enforced on patch payloads
180+
- **RLS enforcement** -- mismatched `user_id` throws `[RLS_Bypass]` on patch, same as upsert
181+
- **PII masking** -- `sensitiveFields` masks values in patch payloads via `write()`
182+
- **Poison pill** -- patch entries dropped after `maxRetries` with `SyncPoisonPill` event
183+
- **Auth expiry** -- `AuthExpiredException` during patch drain stops and preserves entry
184+
- **SQL injection** -- injection strings in patch payloads stored as literals, not executed
185+
170186
---
171187

172188
## Pass criteria
173189

174190
Every release must:
175191

176-
1. Pass all 130 tests in `test/dynos_sync_total_audit_test.dart`
177-
2. Pass all existing tests (66 tests across 7 other test files)
192+
1. Pass all 140 tests in `test/dynos_sync_total_audit_test.dart`
193+
2. Pass all existing tests across other test files
178194
3. Zero `dart analyze` errors or warnings
179195
4. Verified compatibility with Drift and Supabase adapters
180196

example/example.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ Future<void> createTaskViaDao(
7070
await sync.push('tasks', task['id'] as String, task);
7171
}
7272

73-
/// Pattern C: Delete a record
73+
/// Pattern C: Partial update (patch) — only sends changed fields
74+
Future<void> markTaskDone(SyncEngine sync, String id) async {
75+
// Updates only these columns via UPDATE, not upsert.
76+
// Avoids NOT NULL failures from missing columns.
77+
await sync.push(
78+
'tasks',
79+
id,
80+
{
81+
'done': true,
82+
'finished_at': DateTime.now().toUtc().toIso8601String(),
83+
},
84+
operation: SyncOperation.patch);
85+
}
86+
87+
/// Pattern D: Delete a record
7488
Future<void> deleteTask(SyncEngine sync, String id) async {
7589
await sync.remove('tasks', id);
7690
// Deleted locally + queued for remote deletion.

0 commit comments

Comments
 (0)