Skip to content

Commit 45d54de

Browse files
feat(zig): add fuzz parity test, discover empty blob divergence (TASK-127)
- Add zig/harness/test-fuzz-parity.sh: stochastic fuzzer for parity testing - HYPOTHESIS INVALIDATED: Zig reports empty blobs (X'') as NULL in crsql_changes - This is a data corruption bug affecting sync between Zig and Rust/C peers Minimal reproduction: INSERT INTO t VALUES (1, X''); SELECT quote(val) FROM crsql_changes WHERE cid='data'; -- Rust/C: X'' (correct) -- Zig: NULL (incorrect) Follow-up tasks: - TASK-128: Expand parity suite with deterministic regression tests - TASK-129: Fix empty blob handling in Zig implementation
1 parent 650d192 commit 45d54de

File tree

5 files changed

+734
-27
lines changed

5 files changed

+734
-27
lines changed

.tasks/active/TASK-127-experimental-parity-invalidation.md

Lines changed: 0 additions & 27 deletions
This file was deleted.
File renamed without changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# TASK-129: Fix empty blob handling in Zig crsql_changes
2+
3+
## Goal
4+
Fix the divergence where Zig's `crsql_changes` virtual table reports empty blobs (`X''`) as `NULL`.
5+
6+
## Background
7+
Discovered by TASK-127 fuzzing. The Rust/C oracle correctly distinguishes between empty blobs and NULL values, but Zig treats them as equivalent.
8+
9+
## Minimal Reproduction
10+
```sql
11+
CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL, data BLOB);
12+
SELECT crsql_as_crr('t');
13+
INSERT INTO t VALUES (1, X'');
14+
SELECT quote(val) FROM crsql_changes WHERE [table]='t' AND cid='data';
15+
-- Expected: X''
16+
-- Actual (Zig): NULL
17+
```
18+
19+
## Impact
20+
- **Data corruption**: Applications using empty blobs will have incorrect data synced
21+
- **Sync divergence**: Peers will disagree on row values after sync
22+
23+
## Files to Investigate
24+
- `zig/src/changes_vtab.zig` - Changes virtual table implementation
25+
- `zig/src/triggers.zig` - Trigger logic that populates clock tables
26+
- `zig/src/value.zig` (if exists) - Value serialization/deserialization
27+
28+
## Acceptance Criteria
29+
- [ ] Empty blob (`X''`) is reported as `X''` in crsql_changes, not NULL
30+
- [ ] Existing parity tests still pass
31+
- [ ] `test-fuzz-parity.sh` passes with no divergences
32+
33+
## Parent Docs
34+
- TASK-127 (discovered the bug)
35+
- `research/zig-cr/92-gap-backlog.md`
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# TASK-127: Experimentally invalidate "full parity" hypothesis
2+
3+
## Goal
4+
The current hypothesis is that the Zig implementation has achieved full oracle parity with the Rust/C implementation (18/18 tests pass).
5+
We need to **invalidate** this hypothesis experimentally by finding at least one divergence that is not yet covered by our test suite.
6+
7+
## Scope
8+
- Create a new test harness `zig/harness/test-fuzz-parity.sh` (or similar).
9+
- Implement a simple stochastic/fuzzing approach:
10+
- Generate random schemas (tables with random column types, PKs).
11+
- Generate random operations (INSERT, UPDATE, DELETE, transactions).
12+
- Run identical SQL against both Zig (loadable ext) and Rust/C (oracle via sqlite-cr wrapper).
13+
- Compare `crsql_changes`, `crsql_db_version`, `crsql_site_id`, and table contents.
14+
- Run the fuzzer until a divergence is found.
15+
16+
## Files to Modify
17+
- `zig/harness/test-fuzz-parity.sh` (new)
18+
- `zig/harness/test-parity.sh` (optional, to include the new test)
19+
20+
## Acceptance Criteria
21+
- [x] A new test script `zig/harness/test-fuzz-parity.sh` exists.
22+
- [x] The script runs against both Zig and Rust/C oracle.
23+
- [x] The script identifies at least one divergence (a "counter-example" to the full parity hypothesis).
24+
- [x] The divergence is documented in the completion notes.
25+
26+
## Parent Docs
27+
- `research/zig-cr/92-gap-backlog.md`
28+
29+
---
30+
31+
## Progress Log
32+
33+
### 2024-12-20: Task Completed
34+
35+
Created `zig/harness/test-fuzz-parity.sh` - a stochastic fuzzing test that:
36+
- Generates random schemas (1-4 columns, types: INTEGER, TEXT, REAL, BLOB)
37+
- Supports compound primary keys (20% of iterations)
38+
- Generates random operations (INSERT, UPDATE, DELETE)
39+
- Optionally wraps operations in transactions (30% of iterations)
40+
- Includes edge cases: NULL values, empty blobs, empty strings, special characters
41+
- Compares: table contents, db_version, crsql_changes, clock tables
42+
43+
## Completion Notes
44+
45+
### DIVERGENCE FOUND: Empty Blob (`X''`) Handling
46+
47+
**Hypothesis INVALIDATED** - The Zig implementation does NOT have full behavioral parity.
48+
49+
#### Minimal Reproduction
50+
```sql
51+
CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL, data BLOB);
52+
SELECT crsql_as_crr('t');
53+
INSERT INTO t VALUES (1, X'');
54+
SELECT quote(val) FROM crsql_changes WHERE [table]='t' AND cid='data';
55+
```
56+
57+
**Rust/C (oracle)**: `X''` (empty blob)
58+
**Zig**: `NULL`
59+
60+
#### Impact
61+
- The Zig implementation incorrectly reports empty blobs as NULL in `crsql_changes`
62+
- This affects sync correctness: a peer receiving changes from Zig will see NULL instead of empty blob
63+
- This is a **data corruption bug** for applications using empty blobs
64+
65+
#### Root Cause (likely)
66+
The Zig implementation's `crsql_changes` virtual table (or the underlying trigger/value serialization) treats zero-length blobs as NULL when reading values.
67+
68+
#### Follow-up Task
69+
Create TASK-128 to fix empty blob handling in Zig implementation.
70+
71+
### Test Statistics
72+
- Ran 100+ iterations across multiple seeds
73+
- All divergences were consistently the same bug (empty blob → NULL)
74+
- No other divergences found for: table contents, db_version values, clock table contents, non-empty blob handling
75+
76+
### Files Created
77+
- `zig/harness/test-fuzz-parity.sh` - Stochastic parity fuzzer

0 commit comments

Comments
 (0)