Sync: change feed, calternald (full/selective, real-time) and the calternal CLI #17

Closed
opened 2026-09-24 12:52:09 +00:00 by kayg · 17 comments
Owner

Codex job sync.

Base: 8f3a380d27

Codex job `sync`. Base: 8f3a380d2791aadc71dc5d8ecfc7cdbfb624d827
Author
Owner

Starting sync job on branch job/sync at base 8f3a380d27. Read CLAUDE.md, CONTEXT.md, docs/DESIGN.md and crates/plugins/files/DATA-SAFETY.md. Inspecting mutation intents and auth interfaces before implementation.

Starting sync job on branch job/sync at base 8f3a380d2791aadc71dc5d8ecfc7cdbfb624d827. Read CLAUDE.md, CONTEXT.md, docs/DESIGN.md and crates/plugins/files/DATA-SAFETY.md. Inspecting mutation intents and auth interfaces before implementation.
Author
Owner

Finding: files_events is a path-only 10-minute invalidation log, written after mutations. Existing files_index and files_trash_items updates are the durable publication point and startup recovery replays intents before serving. I will attach the cursor feed to those SQLite transactions and keep SSE as a wake-up channel.

Finding: files_events is a path-only 10-minute invalidation log, written after mutations. Existing files_index and files_trash_items updates are the durable publication point and startup recovery replays intents before serving. I will attach the cursor feed to those SQLite transactions and keep SSE as a wake-up channel.
Author
Owner

Finding: the auth API can issue scoped Installation sessions, but the current app has no browser login/authorization UI or CLI grant endpoint. A browser redirect can be added to auth with a one-time code and loopback-only callback; user interaction will need the auth screen when that track lands. Existing Files tus replace already validates captured target item ID and hash at finish, so If-Match can be checked at creation while preserving the finish-time race check.

Finding: the auth API can issue scoped Installation sessions, but the current app has no browser login/authorization UI or CLI grant endpoint. A browser redirect can be added to auth with a one-time code and loopback-only callback; user interaction will need the auth screen when that track lands. Existing Files tus replace already validates captured target item ID and hash at finish, so If-Match can be checked at creation while preserving the finish-time race check.
Author
Owner

Finding: check-generated.sh could not compile calternal-server because apps/web/build did not exist in this worktree (RustEmbed compile error). Running bun install --frozen-lockfile and the real production web build, then regenerating the contract; no product UI files are being changed.

Finding: check-generated.sh could not compile calternal-server because apps/web/build did not exist in this worktree (RustEmbed compile error). Running bun install --frozen-lockfile and the real production web build, then regenerating the contract; no product UI files are being changed.
Author
Owner

Verification finding: extended kill_stress.py to require each acknowledged file after server restart to have a latest feed entry with matching item ID, path, and content hash. python3 crates/plugins/files/kill_stress.py --rounds 30 completed: PASS 30 SIGKILL rounds; seed=12. Exact bytes and quota checks also passed.

Verification finding: extended kill_stress.py to require each acknowledged file after server restart to have a latest feed entry with matching item ID, path, and content hash. python3 crates/plugins/files/kill_stress.py --rounds 30 completed: PASS 30 SIGKILL rounds; seed=12. Exact bytes and quota checks also passed.
Author
Owner

Sync journal finding: advancing a feed cursor per file can lose the remainder of a page after a crash. The new local SQLite journal commits all verified revisions, removals and the page cursor in one FULL-synchronous transaction. Added a failure test that rejects a bad second item and confirms no partial baseline or cursor advance. Local scanner hashes bytes, tracks inode identity through rename chains, and the planner treats edit/delete races as conflicts.

Sync journal finding: advancing a feed cursor per file can lose the remainder of a page after a crash. The new local SQLite journal commits all verified revisions, removals and the page cursor in one FULL-synchronous transaction. Added a failure test that rejects a bad second item and confirms no partial baseline or cursor advance. Local scanner hashes bytes, tracks inode identity through rename chains, and the planner treats edit/delete races as conflicts.
Author
Owner

Transfer finding: downloads now stage in the destination directory, BLAKE3-check and fsync the staged bytes, then create the destination with a no-overwrite hardlink. An old destination goes to OS Trash first; a concurrent new destination makes publication fail without replacement. Tus requests carry the strong content-hash If-Match and expose HEAD/PATCH offsets for resume. Remote folder scans require immutable item IDs, now exposed by Files entries/stat.

Transfer finding: downloads now stage in the destination directory, BLAKE3-check and fsync the staged bytes, then create the destination with a no-overwrite hardlink. An old destination goes to OS Trash first; a concurrent new destination makes publication fail without replacement. Tus requests carry the strong content-hash If-Match and expose HEAD/PATCH offsets for resume. Remote folder scans require immutable item IDs, now exposed by Files entries/stat.
Author
Owner

Correction to the transfer note: final local publication uses rustix renameat_with(NOREPLACE), which maps to renameat2 on Linux and renameatx_np on macOS. This meets the required atomic-rename step while refusing to overwrite a concurrent new destination.

Correction to the transfer note: final local publication uses rustix renameat_with(NOREPLACE), which maps to renameat2 on Linux and renameatx_np on macOS. This meets the required atomic-rename step while refusing to overwrite a concurrent new destination.
Author
Owner

Two-client integration exposed a 400 on tus create: the Files API requires separate base64 filename and path metadata fields. After correcting the client, three files created alternately in two local folders converged with the server (p50 0.289 s, measured edit to second-client visibility). The run also exposed a watcher race: a push wake-up could cancel debounce after it had consumed an inotify event. Debounce now begins only after the event future completes. A 30-round convergence measurement is running.

Two-client integration exposed a 400 on tus create: the Files API requires separate base64 filename and path metadata fields. After correcting the client, three files created alternately in two local folders converged with the server (p50 0.289 s, measured edit to second-client visibility). The run also exposed a watcher race: a push wake-up could cancel debounce after it had consumed an inotify event. Debounce now begins only after the event future completes. A 30-round convergence measurement is running.
Author
Owner

The sequential alternating-create campaign completed 30/30 two-client rounds against a real local server: converged 30/30, latency p50=0.346s p95=0.423s. Each round verified exact bytes in both local folders and the server. This measurement does not yet cover concurrent edits or process kills. Server item identity and feed high-water endpoint were committed as 4f97e59; the journal/daemon slice as c48ea2c.

The sequential alternating-create campaign completed 30/30 two-client rounds against a real local server: `converged 30/30`, `latency p50=0.346s p95=0.423s`. Each round verified exact bytes in both local folders and the server. This measurement does not yet cover concurrent edits or process kills. Server item identity and feed high-water endpoint were committed as 4f97e59; the journal/daemon slice as c48ea2c.
Author
Owner

A new process-kill campaign (daemon SIGKILL each round; server SIGKILL on alternating rounds) failed in round 4/30. The daemon retried HTTP 404 after a tus session reported its final offset but the destination file was not published after restart. The local source bytes remained intact. Resume now verifies the destination before treating a complete offset as committed; when absent, it drops that tus session and starts a fresh create-if-absent upload. Rerunning the campaign.

A new process-kill campaign (daemon SIGKILL each round; server SIGKILL on alternating rounds) failed in round 4/30. The daemon retried HTTP 404 after a tus session reported its final offset but the destination file was not published after restart. The local source bytes remained intact. Resume now verifies the destination before treating a complete offset as committed; when absent, it drops that tus session and starts a fresh create-if-absent upload. Rerunning the campaign.
Author
Owner

After the tus final-offset recovery fix, the alternating daemon/server SIGKILL campaign completed 30/30 rounds: PASS 30 process-kill rounds; seed=17; exact bytes were checked in both folders and the server after each restart. Latency with process restarts included was p50=0.281s p95=5.221s; the uninterrupted 30-round measurement was p50=0.346s p95=0.423s. This campaign still uses create workloads only; edit/rename/conflict cases are being added.

After the tus final-offset recovery fix, the alternating daemon/server SIGKILL campaign completed 30/30 rounds: `PASS 30 process-kill rounds; seed=17`; exact bytes were checked in both folders and the server after each restart. Latency with process restarts included was `p50=0.281s p95=5.221s`; the uninterrupted 30-round measurement was `p50=0.346s p95=0.423s`. This campaign still uses create workloads only; edit/rename/conflict cases are being added.
Author
Owner

The edit/move workload stopped at its first replacement because test folders are on /tmp (tmpfs) while the home Trash is on ext4: local Trash rename returned EXDEV. The old local revision remained intact; no bytes were lost. The daemon now locates the source mount and uses its per-volume freedesktop .Trash-<uid> directory (and .Trashes/<uid> on macOS) when the home Trash is on another device. Rerunning the workload.

The edit/move workload stopped at its first replacement because test folders are on `/tmp` (tmpfs) while the home Trash is on ext4: local Trash rename returned EXDEV. The old local revision remained intact; no bytes were lost. The daemon now locates the source mount and uses its per-volume freedesktop `.Trash-<uid>` directory (and `.Trashes/<uid>` on macOS) when the home Trash is on another device. Rerunning the workload.
Author
Owner

Two-client workload passed an edit, file rename, folder move, and simultaneous conflicting edits; the server and both folders ended with identical bytes and both conflicting revisions. A separate pending-upload integration test inserted real inode/hash journal records at the detected old paths, renamed one file through a case-only chain and moved a parent folder before upload, then started two daemons. It passed: only the final paths were published and pending rows cleared. Mass-deletion integration passed: one-file deletion paused, resume without --confirm failed, --confirm allowed exactly one pass, and the next one-file deletion paused again. CLI integration passed device-code and loopback browser approval, whoami, server-revoking logout, Files, search, share, Notes, and composer commands.

Two-client workload passed an edit, file rename, folder move, and simultaneous conflicting edits; the server and both folders ended with identical bytes and both conflicting revisions. A separate pending-upload integration test inserted real inode/hash journal records at the detected old paths, renamed one file through a case-only chain and moved a parent folder before upload, then started two daemons. It passed: only the final paths were published and pending rows cleared. Mass-deletion integration passed: one-file deletion paused, resume without --confirm failed, --confirm allowed exactly one pass, and the next one-file deletion paused again. CLI integration passed device-code and loopback browser approval, whoami, server-revoking logout, Files, search, share, Notes, and composer commands.
Author
Owner

Campaigns passed: feed 410 full rescan, 64 MiB tus resume after server SIGKILL mid-transfer, 64 MiB local edit during upload with first revision retained in Versions, and 30 seeded random two-client create/edit/rename/move/delete/concurrent steps with exact convergence. A forced EFBIG local write failure left no partial target or temp, and restart recovered exact bytes. Actual ENOSPC was not induced.

Campaigns passed: feed 410 full rescan, 64 MiB tus resume after server SIGKILL mid-transfer, 64 MiB local edit during upload with first revision retained in Versions, and 30 seeded random two-client create/edit/rename/move/delete/concurrent steps with exact convergence. A forced EFBIG local write failure left no partial target or temp, and restart recovered exact bytes. Actual ENOSPC was not induced.
Author
Owner

Finished job/sync at head 0bb9179b07. Worktree clean. Cargo build output cleaned (Removed 18736 files, 12.2GiB total). Built durable Files change feed/conditional operations, two-client sync daemon, browser/device-code CLI login, CLI command surface, and Linux/macOS user-service templates. Auth additions: one-time PKCE-bound device approval, web-cookie-only approval, current-user and self-revocation endpoints.

Gate output (verbatim excerpts):
cargo fmt --check: no output, exit 0.
cargo clippy --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 8.56s (exit 0).
cargo test: test result: ok. 32 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.50s; test result: ok. 354 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s; test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s (511 passed total, 2 ignored, 0 failed).
bash packages/api-client/check-generated.sh: ✨ openapi-typescript 7.13.0; 🚀 ../../contracts/openapi.json → src/generated.ts [178.3ms] (exit 0).
bun run --cwd apps/web check: svelte-check found 0 errors and 0 warnings.
bun run --cwd apps/web test: Test Files 3 passed (3); Tests 9 passed (9).
Campaigns: latency p50=0.344s p95=0.453s (30/30 uninterrupted); PASS 30 process-kill rounds; seed=17; latency p50=0.262s p95=5.289s (restart-inclusive); PASS 30 random create/edit/rename/move/delete/concurrent steps; exact two-client convergence; PASS 30 SIGKILL rounds; seed=12 (Files server feed crash harness). Login device/loopback, pending rename, feed 410, mass deletion, 64 MiB interrupted upload/edit, and local write failure campaigns passed.

Limits: actual ENOSPC was not induced (RLIMIT_FSIZE forced EFBIG); macOS target checks stopped in ring's C build because this Linux host lacks a macOS C cross-compiler/SDK (cc: error: unrecognized command-line option '-arch'). Large-tree latency was not benchmarked; the daemon still scans both trees at each wake-up. The browser approval screen is served by the auth crate because this job does not own the web app UI. No push, deploy, merge, or issue close.

Finished job/sync at head 0bb9179b07d360b74d45d7a2df190042c631988c. Worktree clean. Cargo build output cleaned (`Removed 18736 files, 12.2GiB total`). Built durable Files change feed/conditional operations, two-client sync daemon, browser/device-code CLI login, CLI command surface, and Linux/macOS user-service templates. Auth additions: one-time PKCE-bound device approval, web-cookie-only approval, current-user and self-revocation endpoints. Gate output (verbatim excerpts): `cargo fmt --check`: no output, exit 0. `cargo clippy --all-targets -- -D warnings`: `Finished dev profile [unoptimized + debuginfo] target(s) in 8.56s` (exit 0). `cargo test`: `test result: ok. 32 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.50s`; `test result: ok. 354 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s`; `test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s` (511 passed total, 2 ignored, 0 failed). `bash packages/api-client/check-generated.sh`: `✨ openapi-typescript 7.13.0`; `🚀 ../../contracts/openapi.json → src/generated.ts [178.3ms]` (exit 0). `bun run --cwd apps/web check`: `svelte-check found 0 errors and 0 warnings`. `bun run --cwd apps/web test`: `Test Files 3 passed (3)`; `Tests 9 passed (9)`. Campaigns: `latency p50=0.344s p95=0.453s` (30/30 uninterrupted); `PASS 30 process-kill rounds; seed=17`; `latency p50=0.262s p95=5.289s` (restart-inclusive); `PASS 30 random create/edit/rename/move/delete/concurrent steps; exact two-client convergence`; `PASS 30 SIGKILL rounds; seed=12` (Files server feed crash harness). Login device/loopback, pending rename, feed 410, mass deletion, 64 MiB interrupted upload/edit, and local write failure campaigns passed. Limits: actual ENOSPC was not induced (RLIMIT_FSIZE forced EFBIG); macOS target checks stopped in ring's C build because this Linux host lacks a macOS C cross-compiler/SDK (`cc: error: unrecognized command-line option '-arch'`). Large-tree latency was not benchmarked; the daemon still scans both trees at each wake-up. The browser approval screen is served by the auth crate because this job does not own the web app UI. No push, deploy, merge, or issue close.
Author
Owner

Completed on dev in b9fb570289 (Merge job/sync: change feed, calternald and the calternal CLI (#17)).

Completed on dev in b9fb5702890c49e88453d83a02f33f15034adf8a (Merge job/sync: change feed, calternald and the calternal CLI (#17)).
kayg closed this issue 2026-10-01 05:08:31 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
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
kayg/calternal#17
No description provided.