WIP: chore(sync): kwila sync spec designs #1

Draft
julian wants to merge 2 commits from 001-database-sync into main
Collaborator
No description provided.
julian self-assigned this 2026-08-17 20:45:47 -04:00
julian requested review from addison 2026-08-17 20:53:45 -04:00
@ -0,0 +37,4 @@
- **Vendor the client into a Kwila-owned repository, including its native build pipeline.** The Dart package is pure Dart with no Flutter dependency, but it is a `dart:ffi` wrapper around a Rust `cdylib`. Vendoring therefore means owning `cargo` builds and per-platform native artifact distribution — `cargo-ndk` for Android `jniLibs`, an `.xcframework` for iOS, `.dylib` for macOS, `.dll` for Windows. The project publishes no release binaries, so there is no path that avoids building from source per target.
- Pin by commit SHA. The repository has no GitHub releases, so a release tag is not available to pin against.
- Keep the [Syncable Schema Contract](specs/001-database-sync/001-syncable-schema-contract/SPEC.md) engine-agnostic, so app-side work survives an engine change.
- Rely on the SQLite adapter's materialized tables as the exit path: tenant data must remain readable with ordinary SQL regardless of the engine's fate.
Owner

This is very key!

This is very key!
@ -0,0 +41,4 @@
### The Spike Must Answer
- What is the observed rejection rate against Karriba's real write patterns? Records are written by one person each, so rejections should be near zero. A higher rate is evidence that a server-authoritative engine is the wrong shape for this app. **This is the remaining gate.**
Owner

This spec has some good stuff in it but also reads very poorly because of AI language. I would maybe try writing it in your own words and cutting out some of the slop 😅

This spec has some good stuff in it but also reads very poorly because of AI language. I would maybe try writing it in your own words and cutting out some of the slop 😅
Author
Collaborator

Fair enough 😅

Fair enough 😅
@ -0,0 +20,4 @@
## Requirements
- **No foreign key constraints on synced tables.** Keep the columns and the relationships; drop the enforcement. The engine's schema parser rejects them, and independent offline writes can converge on a state no constraint could have prevented — one device creates a record referencing a parcel while another deletes that parcel. Under RESTRICT this strands the applying side rather than rejecting one write.
Owner

No foreign keys??

No foreign keys??
Author
Collaborator

Tested it, Syncular rejects foreign keys, unique, check, composite PKs and ALTER TABLE ADD COLUMN NOT NULL (SQL defaults do not backfill). Not optional.

We're mostly fine beacuase all 8 RESTRICT FKs already have a canDelete() check in front of them. But that's a UI convention, not enforced in hardDelete(), so those checks need to move into the DAOs.

Two cascades need explicit deletes. The attachments one matters, without it orphan cleanup silently stops and PDFs will pile up.

If we want it properly enforced, it can go in a server-side commit validator.

Tested it, Syncular rejects foreign keys, unique, check, composite PKs and `ALTER TABLE ADD COLUMN NOT NULL` (SQL defaults do not backfill). Not optional. We're mostly fine beacuase all 8 RESTRICT FKs already have a `canDelete()` check in front of them. But that's a UI convention, not enforced in `hardDelete()`, so those checks need to move into the DAOs. Two cascades need explicit deletes. The attachments one matters, without it orphan cleanup silently stops and PDFs will pile up. If we want it properly enforced, it can go in a server-side commit validator.
Owner

Please don't post AI comments.

Please don't post AI comments.
@ -0,0 +29,4 @@
- **An empty-string relationship column means "not yet set".** A row can briefly exist mid-merge with a defaulted foreign key column holding `''`, which references nothing. Queries must treat an empty-string relationship column as unset rather than as a value, in the same way they treat orphaned references — otherwise the row is either invisible to joins or silently wrong.
- **Replace every load-bearing CASCADE with an explicit delete in the same transaction.** Dropping foreign keys removes cascade behaviour that application code may be relying on without stating it. In Karriba, `SprayRecordsDao.deleteMultiple()` deletes spray records and never touches `spray_product`, and `AttachmentsDao.deleteSprayRecordsAndFindOrphans()` assumes cascade has already removed link rows before it computes orphans — without it, `hasReferences()` reports attachments as still linked, orphan detection silently stops finding anything, and attachment files are never cleaned up.
- **No triggers on synced tables.** Merge activity writes directly, so a trigger can fire multiple times per logical change. Karriba's schema and migration history contain none, so this is already satisfied.
- **Schema is defined by SQL DDL migrations, not by an ORM.** The engine derives its schema from `NNNN_name/up.sql` with a checksum lock file; locked migrations may never be edited, removed, renamed or reordered. An app cannot have two schema owners, so an ORM's migration system is retired for synced tables. For Karriba this retires the Drift migrations and requires a one-time data migration of existing installs.
Owner

Yes for sure! ORM can have major drawbacks. I would rather a query-builder or hand-written queries.

Yes for sure! ORM can have major drawbacks. I would rather a query-builder or hand-written queries.
This pull request is marked as a work in progress.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 001-database-sync:001-database-sync
git switch 001-database-sync

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff 001-database-sync
git switch 001-database-sync
git rebase main
git switch main
git merge --ff-only 001-database-sync
git switch 001-database-sync
git rebase main
git switch main
git merge --no-ff 001-database-sync
git switch main
git merge --squash 001-database-sync
git switch main
git merge --ff-only 001-database-sync
git switch main
git merge 001-database-sync
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kwila/sync!1
No description provided.