Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
aad64c5
sync: lists merge by a per-path policy (Atomic, or Append: by positio…
adamski Sep 29, 2026
feefd76
sync: a same-length list under Append still collides at the list when…
adamski Sep 29, 2026
6ba4187
sync: under Append, a base list with a repeated value collides whole …
adamski Sep 29, 2026
12b0ebb
test: property model edits a list on both sides under Append and Atom…
adamski Sep 29, 2026
9a4904a
test: property model also checks list merges at catch-up pages, requi…
adamski Sep 29, 2026
5b2ba6a
sync: a delete names the version it was made on; a newer version from…
adamski Sep 30, 2026
6d74979
test: property model server refuses stale deletes and counts deletes …
adamski Sep 30, 2026
dcb6226
test: property model asserts a delete never lands on a version the cl…
adamski Sep 30, 2026
d472e64
sync: the shadow is the upload reply's content; pin the patches the c…
adamski Sep 30, 2026
c5b04ed
sync: a clock_skew join is re-signed once on the same socket with the…
adamski Sep 30, 2026
6bbd58c
sync: park a document whose upload replies keep differing from what w…
adamski Sep 30, 2026
a31cc0d
test: patch fixture covers exponent-form numbers and index-like keys
adamski Sep 30, 2026
95e50b7
test: the learnt clock offset carries to the next reconnect
adamski Sep 30, 2026
fc07432
sync: a pending delete is sent even when earlier rows are parked, so …
adamski Sep 30, 2026
6e06a92
sync: uploads wait for own's first changes answer, not a nonzero curs…
adamski Sep 30, 2026
40fa353
sync: a refused upload keeps its sent marks, so a snapshot cannot sil…
adamski Sep 30, 2026
3157039
sync: a delete the server refused parks like any other refused upload…
adamski Sep 30, 2026
852cd85
test: the list order invariant checks only pairs both sides hold
adamski Sep 30, 2026
df4f9ce
sync: a delete on a document this device never got a server version f…
adamski Sep 30, 2026
4622a46
sync: a delete on a migrated document with no server version fetches …
adamski Sep 30, 2026
19f15f7
test: the list order invariant also checks pairs only one side holds
adamski Sep 30, 2026
9fa355b
sync: a delete the server refuses restores the server's version and k…
adamski Sep 30, 2026
dec252d
store: local writes read the data dir's user inside their transaction
adamski Sep 30, 2026
bab0f95
engine: start creates the data dir's identity row, retries an open th…
adamski Sep 30, 2026
2767c4b
engine: a sign-out halts as not enrolled, every join reads the stored…
adamski Sep 30, 2026
dc3fb5f
sync: a pending delete on a document that became a publication leaves…
adamski Sep 30, 2026
532a514
store: migrate v1 sync data into shadows, outbox markers and scopes o…
adamski Sep 30, 2026
e5bafa2
store: write the v1 backup under the write lock to a temporary file a…
adamski Sep 30, 2026
ea23aa7
sync: a publication equal to the pending delete's local content keeps…
adamski Sep 30, 2026
686d314
store: v1 data without a user_config row fails the migration, and ski…
adamski Sep 30, 2026
62da433
test: the torn-read test proves no join goes out, and the sign-out ch…
adamski Sep 30, 2026
d60bdb9
store: notices name the kept copy they report, and a whole copy can b…
adamski Sep 30, 2026
0462df6
store: a pending v1 edit on a document that isn't ours is kept aside …
adamski Sep 30, 2026
25d2cad
store: the v1 backup re-checks for v1 data under its lock, and the dr…
adamski Sep 30, 2026
34fb79b
store: every v1 document whose queued work does not become an upload …
adamski Sep 30, 2026
fc4e3d3
store: a kept copy attaches to the next notice only
adamski Sep 30, 2026
5bae033
store: the host's document reads, counts and search, without the v1 d…
adamski Sep 30, 2026
0fdbb7d
host: one engine per data dir shared by every handle, seeded bounded …
adamski Sep 30, 2026
79e7f65
host: a credential change waits only for engines on its own data dir;…
adamski Sep 30, 2026
2d0dcb9
store: pending and parked split documents exactly as the uploader doe…
adamski Sep 30, 2026
79f8a1b
store: a v1 document deleted elsewhere keeps its unsent edits aside; …
adamski Sep 30, 2026
fb7f2d0
host: dial attempts can't evict a kept-copy notice, identity is annou…
adamski Sep 30, 2026
96a558f
test: the v1 migration's tests check the counts it returns instead of…
adamski Sep 30, 2026
4d03fd1
remove the v1 facade crate, the seed tool, the v1 examples and the JU…
adamski Sep 30, 2026
c0a28d4
ffi: C API v2 over the per-data-dir engine registry; remove the v1 cl…
adamski Sep 30, 2026
de916f2
ffi: a malformed search query is invalid input; document the unload c…
adamski Sep 30, 2026
9b3a4cd
ffi: the error callback names its scope, callback types carry the Rep…
adamski Sep 30, 2026
ea474c4
ffi: list, dismiss and restore kept copies, whole or per field
adamski Sep 30, 2026
0a000a1
release: every binary carries its replicant-client version, and a scr…
adamski Sep 30, 2026
884fb8b
ffi: the header exports only the API, the ABI version is major.minor,…
adamski Sep 30, 2026
83c12b7
ffi: a claim stores its credentials before filling the buffers, crede…
adamski Sep 30, 2026
b5fb272
ffi: failed reads null their out pointers, a config email must be UTF…
adamski Sep 30, 2026
9feadf7
ffi: a claim checks the data dir before spending the code and returns…
adamski Sep 30, 2026
0e7b292
wrapper: C++ wrapper for the v2 C API (Config, SyncException::result,…
adamski Sep 30, 2026
7c63561
remove replicant-core and the v1 test tooling; document the v2 C API;…
adamski Sep 30, 2026
8235525
ffi: secrets never cross the C API — remove load/store credentials; c…
adamski Sep 30, 2026
71eb091
wrapper: packed ABI compare (g++ -Wtype-limits clean), value-initiali…
adamski Sep 30, 2026
646c4c3
engine: a signed-out engine re-reads the stored credentials every 3 s…
adamski Sep 30, 2026
47eb345
engine: a sign-out from any credential halt reports NotEnrolled, and …
adamski Sep 30, 2026
595a64d
fanout: undelivered dials collapse only into a dial with no success o…
adamski Sep 30, 2026
306a20a
engine: an AccountDisabled halt re-reads the stored credentials every…
adamski Sep 30, 2026
0117796
store: a v1 backup that exists is never replaced (a later migration w…
adamski Sep 30, 2026
7dcf51f
ffi: restore_fields on a whole-document copy is invalid input and kee…
adamski Sep 30, 2026
5026aa4
test: push_gap_triggers_catch_up counts own-scope snapshot and change…
adamski Sep 30, 2026
b1467f4
engine: a sign-out clears refused credentials, so the same keys store…
adamski Sep 30, 2026
2dd8227
machine: a credential recheck left from an earlier halt never dials a…
adamski Sep 30, 2026
5582361
fanout: a dial collapses only into a dial queued last, so every event…
adamski Sep 30, 2026
0cad564
store: a v1 file is backed up once, and again only after a backup is …
adamski Sep 30, 2026
865dd6f
ffi: ErrorMigrationFailed documents both a failed backup and a migrat…
adamski Sep 30, 2026
65d9b27
test: a second opener that takes the lock before 013 runs adds no v1 …
adamski Sep 30, 2026
ba70ff4
engine: create reads the stored credentials before it returns, so the…
adamski Oct 1, 2026
f598bcd
ffi: a failed create nulls the out handle on every path, a null confi…
adamski Oct 1, 2026
612561e
ffi: process_events checks the thread before the pump flag, so a wron…
adamski Oct 1, 2026
a836126
ffi: enroll_request refuses a non-https url or a bad email as invalid…
adamski Oct 1, 2026
d53137c
ffi: header states that the config email is not part of the shared-en…
adamski Oct 1, 2026
05a2fc0
docs: CHANGELOG lists the 0.6 error codes that are gone and the three…
adamski Oct 1, 2026
ac10260
docs: README states that the static library exports the bundled SQLit…
adamski Oct 1, 2026
aceaf7e
ffi: document JSON names its owner owner_id, and the header documents…
adamski Oct 1, 2026
16b7e50
ffi: titles come only from the JSON Pointer the app sets in Replicant…
adamski Oct 1, 2026
a9156ba
docs: README and CHANGELOG describe title_pointer: titles come only f…
adamski Oct 1, 2026
6ee6c3b
docs: the title_pointer check covers one process; every process on a …
adamski Oct 1, 2026
9252bf1
host: stored credentials that can never be read (key missing, not dec…
adamski Oct 1, 2026
2aff55e
test: the damaged-credentials test comment describes corruption, not …
adamski Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 1 addition & 62 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,72 +44,11 @@ jobs:
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

- name: Run unit tests
run: cargo test --workspace --lib
run: cargo test -p replicant-client --lib --tests

- name: Run doc tests
run: cargo test --workspace --doc

integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
env:
SQLX_OFFLINE: true

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: replicant_server_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v3

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: '1.17'
otp-version: '27'

# The harness boots the stripped server (a library: no HTTP server of its
# own) behind a minimal endpoint that mounts ReplicantServer.Sync.Socket on
# :4000, seeds an enrolled user + credential (and one legacy nil-user
# credential for the negative test), and runs the gated integration suite.
# It clones the server itself at the pinned SERVER_REF, so no separate
# checkout/credential/health steps are needed here.
- name: Boot stripped server + run integration suite
env:
REPLICANT_SERVER_REF: '19ffa7a' # origin/main (PR #8 + #9 merged: canonical hash + sender-echo fix)
REPLICANT_SERVER_DIR: /tmp/replicant-server-interop
INTEROP_DB_HOST: localhost
INTEROP_DB_USER: postgres
INTEROP_DB_PASS: postgres
INTEROP_DB_NAME: replicant_server_test
run: ./test/run_phoenix_interop_local.sh

lint:
name: Lint
runs-on: ubuntu-latest
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## Unreleased

### Breaking: protocol v2 and C API v2 (server 0.5.0 required)
- One engine per data dir per process, shared by every handle; `replicant_create` takes a `ReplicantConfig` and never waits on the network.
- New: `replicant_get_state`, `replicant_reconnect`, `replicant_abi_version` (C API v2 is ABI 1.0: `REPLICANT_ABI_VERSION_MAJOR`/`MINOR`), the C++ wrapper `replicant.hpp`, Kept copies (`replicant_list_recovered`, `replicant_dismiss_recovered`, `replicant_restore_document`, `replicant_restore_fields`), `replicant_list_parked`, `DatabaseChanged`, `IdentityAdopted`, `EventOrigin::OtherProcess`, results `ErrorNewerSchema`, `ErrorMigrationFailed`, `ErrorBusy`, `ErrorWrongThread`, `ErrorNoCallbacks`, `ErrorDocumentGone`, `ErrorBufferTooSmall`, `ErrorTokenRejected`, `ReplicantConfig.list_merge`, `struct_size` on `ReplicantConfig` and `ReplicantState`, error code `Diverged` (and `DeleteRefused` reserved), kept-copy reasons `delete_superseded`, `delete_refused`, `delete_publication` and `unmigratable`.
- Error codes: 1002 `InvalidSignature`, 2001 `ConnectionFailed`, 2002 `Timeout`, 2102 `InvalidTimestamp`, 3001 `MissingParams`, 3002 `TopicUserMismatch` and 5001 `UpdateConflict` are gone (a host that still matches them never sees them). Renamed, same number: 1001 `InvalidApiKey` → `AuthInvalid`, 1003 `CredentialNotEnrolled` → `NotEnrolled`, 2101 `TimestampExpired` → `ClockSkew`.
- Removed from callbacks and reads: `IdentityChanged`, `author_name`, `document_count`, `sync_revision`; `user_id` is now `owner_id` in callbacks and in the document JSON of `replicant_get_document`, `replicant_get_all_documents` and `replicant_search_documents`. C constants of the result, error-code, event and origin enums carry their type name as a prefix.
- Changed: `DocumentChanged` replaces Created/Updated; callbacks carry owner, author, visibility, read-only, document ids and kept copies on errors, and conflict reasons; deletes are conditional (a newer version from another device undoes a delete, reported as `ConflictDetected` `delete_superseded`); `replicant_enroll_claim` takes the data dir, stores the credentials itself and returns only the user id; one callback per kind (registering again replaces it), and callbacks run only on the thread that first registered; `create_document_with_id` refuses an existing or deleted id.
- An engine halted as signed out, or on refused credentials (`AuthInvalid`, `AccountDisabled`, `identity_drift`), re-reads the stored credentials every 3 s, so a sign-in in another process (the standalone app) reaches open plugins within about 3 s. Credentials the server refused are not tried again until they change. A sign-out while halted on a credential or account refusal reports `NotEnrolled`. Stored credentials that can never be read (key file missing, credentials not decryptable) count as signed out: `NotEnrolled`.
- Removed: the v1 client, `replicant-core`, `replicant-seed`, the JUCE wrapper; `replicant_store_credentials`, `replicant_load_credentials` and `REPLICANT_CREDENTIAL_MAX_LEN` (secrets never cross the C API: sign in with `replicant_enroll_claim`, sign out with `replicant_clear_credentials`, and read sign-in status from `replicant_get_state` and `replicant_get_user_id`).
- The first open migrates a v1 database, after copying it to `<database>.v1-backup` (or `<database>.v1-backup-<unix seconds>` when that exists; a backup is never replaced, and a second is written only for a file restored from a backup); rows it cannot read are kept as `unmigratable` copies. After that, older builds cannot open it (`VersionMissing`): upgrade the app and every plugin format together and quit hosts before installing.
- Titles: the library knows no content keys. `ReplicantConfig.title_pointer` (a JSON Pointer, e.g. `"/title"`) names the string that is a document's `title` in reads, callbacks, kept copies and the `title:` search field; without it every title is null. The server's title is no longer used. Every process on a data dir must pass the same pointer: the shared-engine check (`ErrorConfigMismatch`) covers handles in one process only, so a different pointer in another process is not detected. Each launch with a different pointer recomputes every title and the search index, and titles end up mixed while both run (migration 016 records the pointer used).

## 0.6.4

Patch release: no API change. Event timing changes for hosts that relied on the
Expand Down
Loading
Loading