Sharing slice 1 follow-up: durable offline recovery and lifecycle controls #876

Closed
kayg wants to merge 129 commits from review/sharing-pr3-review into dev
Owner

Restack 11-09 (onto #842 8e2e3a948, which is on #909)

The old head 9fec3d1f8 is kept at archive/review/sharing-pr3-review-9fec3d1f. All 89 commits were replayed with cherry-pick -x. Where they met changes since:

  • #909's hardened owner fast path wins where #876 re-did the same thing: room authorization reuse, the owner-cache gate. #909 was the review of these commits.
  • #909's sweep change is kept. The server-wide cleanup_sharing no longer runs inside requests; it moved to a throttled background sweep. #876's runtime full-reset drops its reset-required early returns, and requests mark only their own dormant Installation. wake_fanout is kept next to the sweep scheduling.
  • Day-restore hooks dropped, with their 3 isolation tests: dev no longer restores a deleted day by its id (a deleted day gets a successor), so there's nothing to fence.
  • The web side goes through #842's lazy split. The controls, pending-edit and closed-copy helpers load only when a share ends:
    • share-controls and sharing-sync chunks;
    • the new @calternal/editor/shared-copy entry and offline/closed-copy.ts;
    • diff-match-patch, imported on demand.
  • First paint: first_js_brotli 223.902 KB vs #842's 222.995 KB (+0.91 KB, for the recovery/freeze logic in every open editor and peek). Approved by kayg 11-09, offset by the credits PR. Lines are set in perf/budgets.json.

Gates and perf follow in comments.


Data-safety follow-up plan

  • Drain committed share_fanout intents in a background worker, in ordered batches of at most 64 recipient journal appends. The source transaction only signals the worker after commit; startup and periodic sweeps recover missed signals. A stable (owner, note, note_seq, recipient) key plus a durable delivery record makes retries safe across a crash between journal append and source acknowledgement.
  • Expose the existing full-reset prepare/ack path to the sharing sync client. Apply pending controls before snapshot acknowledgement, fetch the current authorized snapshot on re-grant or installation reset, omit deleted notes under Q14, and include them again after restore.
  • Revalidate the WebSocket caller's credential and scopes before each catch-up page and before each incoming message. On revocation, send unauthorized, close the socket, and let the client sign out and stop syncing.

Sizes and complexity

Fan-out adds O(1) work to a source commit (notification only). The worker handles at most 64 recipient journal entries per batch; selection and delivery state are indexed by source sequence and recipient. Full reset work is O(S) for S currently authorized shared notes and is acknowledged only after the client durably applies controls and snapshots. WebSocket revalidation is O(1) indexed credential/Installation lookup per page or message.

Failure table

Failure Effect Detection Recovery Test
Commit finishes before worker notification or process restart Recipient journal is behind Pending source intent / delivery age Startup and periodic sweeps drain in sequence Restart/sweep Rust regression and online/offline Playwright case
Crash after recipient append before delivery mark The same intent is retried Missing durable delivery row record_once with the source sequence key prevents a duplicate journal entry Idempotent retry Rust regression
Deleted share appears during reset preparation One deleted note could abort every snapshot Deleted lifecycle fence Skip it in the full snapshot; Q14 restore creates a new baseline Deleted/reset/restore Rust regression
App password is revoked while its socket is lagged Cached caller could replay journal or actions Credential lookup fails before catch-up/message handling Send unauthorized, close, and stop client syncing Revoked credential catch-up Rust regression

Gates

Rust regressions cover fan-out ordering/retry, full-reset invocation/deleted-note handling, and revoked WebSocket callers. The added Playwright case runs admin-sharing.spec.ts with CI=1 --retries=0 --workers=1 on dev-01 Chromium and dev-02 WebKit. The 200-append perf harness on both perf VMs must keep owner-only appends within 1% of the matching dev baseline.

Stacks on #842 via feat/sharing-s1-pr3.

Changes

  • Add per-note editor forget that closes open sessions and removes IndexedDB, WAL, preview cache, and per-note metadata.
  • Before a purge control is acknowledged, rescue unconfirmed local edits into the recipient's deterministic Your edits to ‹title› note. The recovery reads both open editor state and a closed copy's IndexedDB/WAL state.
  • Commit owner delete/restore transitions under the per-note sharing writer lock, advance the lifecycle fence, preserve active grants across restore, and push content-free recipient journal events.
  • Route task status changes through the same shared-note admission path.

Verification

  • Rust touched crates on dev-01: cargo test --locked -j4 -p calternal-auth -p calternal-core -p calternal-engine -p calternal-notes -p calternal-server -p calternal-attachments -p calternal-search -p calternal -p calternal-daily -p calternal-tasks -p calternal-contacts-actions — 1117 passed, 0 failed, 20 ignored.
  • Web check on dev-01: cd apps/web && bun run check — 0 errors, 14 warnings.
  • Web Vitest on dev-01: cd apps/web && bun run test — 84 passed.
  • Editor Vitest on dev-01: cd packages/editor && bun run test — 519 passed.
  • admin-sharing.spec.ts, CI=1, --retries=0, --workers=1: Chromium dev-01 — 7 passed; WebKit dev-02 — 7 passed.
## Restack 11-09 (onto #842 `8e2e3a948`, which is on #909) The old head `9fec3d1f8` is kept at `archive/review/sharing-pr3-review-9fec3d1f`. All 89 commits were replayed with `cherry-pick -x`. Where they met changes since: - **#909's hardened owner fast path wins** where #876 re-did the same thing: room authorization reuse, the owner-cache gate. #909 was the review of these commits. - **#909's sweep change is kept.** The server-wide `cleanup_sharing` no longer runs inside requests; it moved to a throttled background sweep. #876's runtime full-reset drops its reset-required early returns, and requests mark only their own dormant Installation. `wake_fanout` is kept next to the sweep scheduling. - **Day-restore hooks dropped**, with their 3 isolation tests: dev no longer restores a deleted day by its id (a deleted day gets a successor), so there's nothing to fence. - **The web side goes through #842's lazy split.** The controls, pending-edit and closed-copy helpers load only when a share ends: - `share-controls` and `sharing-sync` chunks; - the new `@calternal/editor/shared-copy` entry and `offline/closed-copy.ts`; - `diff-match-patch`, imported on demand. - **First paint:** first_js_brotli 223.902 KB vs #842's 222.995 KB (**+0.91 KB**, for the recovery/freeze logic in every open editor and `peek`). Approved by kayg 11-09, offset by the credits PR. Lines are set in `perf/budgets.json`. Gates and perf follow in comments. --- ## Data-safety follow-up plan - Drain committed `share_fanout` intents in a background worker, in ordered batches of at most 64 recipient journal appends. The source transaction only signals the worker after commit; startup and periodic sweeps recover missed signals. A stable `(owner, note, note_seq, recipient)` key plus a durable delivery record makes retries safe across a crash between journal append and source acknowledgement. - Expose the existing full-reset prepare/ack path to the sharing sync client. Apply pending controls before snapshot acknowledgement, fetch the current authorized snapshot on re-grant or installation reset, omit deleted notes under Q14, and include them again after restore. - Revalidate the WebSocket caller's credential and scopes before each catch-up page and before each incoming message. On revocation, send `unauthorized`, close the socket, and let the client sign out and stop syncing. ### Sizes and complexity Fan-out adds O(1) work to a source commit (notification only). The worker handles at most 64 recipient journal entries per batch; selection and delivery state are indexed by source sequence and recipient. Full reset work is O(S) for S currently authorized shared notes and is acknowledged only after the client durably applies controls and snapshots. WebSocket revalidation is O(1) indexed credential/Installation lookup per page or message. ### Failure table | Failure | Effect | Detection | Recovery | Test | |---|---|---|---|---| | Commit finishes before worker notification or process restart | Recipient journal is behind | Pending source intent / delivery age | Startup and periodic sweeps drain in sequence | Restart/sweep Rust regression and online/offline Playwright case | | Crash after recipient append before delivery mark | The same intent is retried | Missing durable delivery row | `record_once` with the source sequence key prevents a duplicate journal entry | Idempotent retry Rust regression | | Deleted share appears during reset preparation | One deleted note could abort every snapshot | Deleted lifecycle fence | Skip it in the full snapshot; Q14 restore creates a new baseline | Deleted/reset/restore Rust regression | | App password is revoked while its socket is lagged | Cached caller could replay journal or actions | Credential lookup fails before catch-up/message handling | Send `unauthorized`, close, and stop client syncing | Revoked credential catch-up Rust regression | ### Gates Rust regressions cover fan-out ordering/retry, full-reset invocation/deleted-note handling, and revoked WebSocket callers. The added Playwright case runs `admin-sharing.spec.ts` with `CI=1 --retries=0 --workers=1` on dev-01 Chromium and dev-02 WebKit. The 200-append perf harness on both perf VMs must keep owner-only appends within 1% of the matching dev baseline. Stacks on #842 via `feat/sharing-s1-pr3`. ## Changes - Add per-note editor forget that closes open sessions and removes IndexedDB, WAL, preview cache, and per-note metadata. - Before a purge control is acknowledged, rescue unconfirmed local edits into the recipient's deterministic `Your edits to ‹title›` note. The recovery reads both open editor state and a closed copy's IndexedDB/WAL state. - Commit owner delete/restore transitions under the per-note sharing writer lock, advance the lifecycle fence, preserve active grants across restore, and push content-free recipient journal events. - Route task status changes through the same shared-note admission path. ## Verification - Rust touched crates on dev-01: `cargo test --locked -j4 -p calternal-auth -p calternal-core -p calternal-engine -p calternal-notes -p calternal-server -p calternal-attachments -p calternal-search -p calternal -p calternal-daily -p calternal-tasks -p calternal-contacts-actions` — 1117 passed, 0 failed, 20 ignored. - Web check on dev-01: `cd apps/web && bun run check` — 0 errors, 14 warnings. - Web Vitest on dev-01: `cd apps/web && bun run test` — 84 passed. - Editor Vitest on dev-01: `cd packages/editor && bun run test` — 519 passed. - `admin-sharing.spec.ts`, `CI=1`, `--retries=0`, `--workers=1`: Chromium dev-01 — 7 passed; WebKit dev-02 — 7 passed.
kayg added 55 commits 2026-11-02 17:58:14 +00:00
fix(sharing): lock daily blake3 dependency
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
22b9d347e7
Author
Owner

Second-round data-safety findings and gates

Verified on head 1b31fa0aa84e279bfd2be77ebb0f16f3f8a4675c.

  1. Fan-out drain: Added a durable bounded worker, woken after commit and swept at startup/periodically. Per-recipient sequence/fence receipts make delivery idempotent and resumable; the writer does not wait for recipient delivery. committed_fanout_reaches_the_recipient_journal, fanout_record_once_recovers_after_publication_before_receipt, and fanout_delivery_order_uses_note_sequence_not_wall_clock were run red before their fixes and pass now. The offline catch-up browser case was red before the fix and passes on Chromium and WebKit.
  2. Full reset: Connected full snapshots to re-grant and device-fence mismatch recovery. Preparation skips deleted/trashed notes with a recorded outcome; restore returns the note to the recipient view. regrant_full_snapshot_is_available_through_the_sharing_action, deleted_shared_notes_are_skipped_by_a_full_reset_and_return_after_restore, and full_reset_blocks_shared_writes_until_snapshot_acknowledgement pass. The deleted-note and re-grant regressions were run red first.
  3. WebSocket credential revalidation: Catch-up revalidates the caller's credential and scope before replay. A revoked app password closes with the auth code, and the client signs out and stops syncing. live_revalidation_observes_app_password_revocation and revoked_app_password_is_closed_before_catch_up_replays_entries were run red first and pass now.

Final gates

  • PASS, dev-01: ~/bin/slot nix develop -c bash -c 'cargo test --locked -j4 -p calternal-auth' — 71 library unit tests and the package integration suites passed; expected perf-only tests were ignored.
  • PASS, dev-01: ~/bin/slot nix develop -c bash -c 'cargo test --locked -j4 -p calternal-server --test signout' — 2 passed.
  • PASS, dev-01 Chromium: ~/bin/slot nix develop -c bash -c 'CI=1 CALTERNAL_BIN=/home/claude/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-chromium-1440 --workers=1 --retries=0 --grep=catches' — setup plus the offline catch-up case passed.
  • PASS, dev-02 WebKit: ~/bin/slot nix develop -c bash -c 'CI=1 CALTERNAL_BIN=/home/cua/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-webkit-1440 --workers=1 --retries=0 --grep=catches' — setup plus the offline catch-up case passed.

200-append performance

Both unoptimized binaries were built on dev-01 from dev 55f35fcde3bc91ca3781055577b93e93819f6cd4 and this PR head, relayed through the control host, and SHA-256 verified on both perf VMs. Each VM ran 20 alternating pairs (40 runs), with a fresh Home and 1,000-line note per run. Startup, creation, verification, and shutdown were outside the timed interval; all 200 append markers appeared exactly once in every run.

VM dev median / p90 (s) candidate median / p90 (s) median delta
perf-01 0.711903 / 0.727145 0.712531 / 0.754561 +0.088%
perf-02 0.714975 / 0.732433 0.721419 / 0.767551 +0.901%

Build command on dev-01 in each worktree: ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo build --locked -j4 -p calternal --bin calternal'.

Measurement command on each perf VM: ~/bin/perf-lock python3 /tmp/sharing-pr3-append-bench.py --dev /home/claude/sharing-pr3-perf-final/dev --candidate /home/claude/sharing-pr3-perf-final/candidate --pairs 20 --root /tmp/sharing-pr3-perf-final-r10 --json-out /home/claude/sharing-pr3-perf-final-r10.json.

Both medians are within the 1% gate. Binary SHA-256: dev e1fb7c00f4fe39c244aec6a5fcf68ad025ae7e871157ed2ab9c7387055adc3a9; candidate 14a9614c4c79b0b984fe07e280fc96ad01b5d77b6373242bee1ccabc52ad602a.

## Second-round data-safety findings and gates Verified on head `1b31fa0aa84e279bfd2be77ebb0f16f3f8a4675c`. 1. **Fan-out drain:** Added a durable bounded worker, woken after commit and swept at startup/periodically. Per-recipient sequence/fence receipts make delivery idempotent and resumable; the writer does not wait for recipient delivery. `committed_fanout_reaches_the_recipient_journal`, `fanout_record_once_recovers_after_publication_before_receipt`, and `fanout_delivery_order_uses_note_sequence_not_wall_clock` were run red before their fixes and pass now. The offline catch-up browser case was red before the fix and passes on Chromium and WebKit. 2. **Full reset:** Connected full snapshots to re-grant and device-fence mismatch recovery. Preparation skips deleted/trashed notes with a recorded outcome; restore returns the note to the recipient view. `regrant_full_snapshot_is_available_through_the_sharing_action`, `deleted_shared_notes_are_skipped_by_a_full_reset_and_return_after_restore`, and `full_reset_blocks_shared_writes_until_snapshot_acknowledgement` pass. The deleted-note and re-grant regressions were run red first. 3. **WebSocket credential revalidation:** Catch-up revalidates the caller's credential and scope before replay. A revoked app password closes with the auth code, and the client signs out and stops syncing. `live_revalidation_observes_app_password_revocation` and `revoked_app_password_is_closed_before_catch_up_replays_entries` were run red first and pass now. ### Final gates - **PASS, dev-01:** `~/bin/slot nix develop -c bash -c 'cargo test --locked -j4 -p calternal-auth'` — 71 library unit tests and the package integration suites passed; expected perf-only tests were ignored. - **PASS, dev-01:** `~/bin/slot nix develop -c bash -c 'cargo test --locked -j4 -p calternal-server --test signout'` — 2 passed. - **PASS, dev-01 Chromium:** `~/bin/slot nix develop -c bash -c 'CI=1 CALTERNAL_BIN=/home/claude/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-chromium-1440 --workers=1 --retries=0 --grep=catches'` — setup plus the offline catch-up case passed. - **PASS, dev-02 WebKit:** `~/bin/slot nix develop -c bash -c 'CI=1 CALTERNAL_BIN=/home/cua/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-webkit-1440 --workers=1 --retries=0 --grep=catches'` — setup plus the offline catch-up case passed. ### 200-append performance Both unoptimized binaries were built on dev-01 from `dev` `55f35fcde3bc91ca3781055577b93e93819f6cd4` and this PR head, relayed through the control host, and SHA-256 verified on both perf VMs. Each VM ran 20 alternating pairs (40 runs), with a fresh Home and 1,000-line note per run. Startup, creation, verification, and shutdown were outside the timed interval; all 200 append markers appeared exactly once in every run. | VM | dev median / p90 (s) | candidate median / p90 (s) | median delta | |---|---:|---:|---:| | perf-01 | 0.711903 / 0.727145 | 0.712531 / 0.754561 | +0.088% | | perf-02 | 0.714975 / 0.732433 | 0.721419 / 0.767551 | +0.901% | Build command on dev-01 in each worktree: `~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo build --locked -j4 -p calternal --bin calternal'`. Measurement command on each perf VM: `~/bin/perf-lock python3 /tmp/sharing-pr3-append-bench.py --dev /home/claude/sharing-pr3-perf-final/dev --candidate /home/claude/sharing-pr3-perf-final/candidate --pairs 20 --root /tmp/sharing-pr3-perf-final-r10 --json-out /home/claude/sharing-pr3-perf-final-r10.json`. Both medians are within the 1% gate. Binary SHA-256: dev `e1fb7c00f4fe39c244aec6a5fcf68ad025ae7e871157ed2ab9c7387055adc3a9`; candidate `14a9614c4c79b0b984fe07e280fc96ad01b5d77b6373242bee1ccabc52ad602a`.
Author
Owner

Final sharing follow-up and stack rebase

Verified at #876 head 9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b. The stack is based on the live #827 head 76f11dc4ba06468a2a0ed4d4592cea6038206696; requested commit 0543321b is an ancestor. The single-authorization-under-the-lock behavior remains intact.

PR Head Base
#829 5cab44a4afdc932ce0afca65b61cb13137afe946 #827 76f11dc4ba06468a2a0ed4d4592cea6038206696
#835 3755691ddc63a740d36a4782f2163a3876f58cfc #829 5cab44a4afdc932ce0afca65b61cb13137afe946
#842 0fb5e8ed3c161b70443b63c1649484fda758e5a6 #835 3755691ddc63a740d36a4782f2163a3876f58cfc
#876 9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b #842 0fb5e8ed3c161b70443b63c1649484fda758e5a6

Findings

  1. Fan-out drain: PASS. The bounded durable worker drains committed intents, wakes after commit, and sweeps on startup/periodically. Per-recipient sequence/fence receipts make it idempotent and crash-resumable without holding the writer path. The Rust regressions were run red before the fixes and now pass: committed_fanout_reaches_the_recipient_journal, fanout_record_once_recovers_after_publication_before_receipt, and fanout_delivery_order_uses_note_sequence_not_wall_clock. The offline catch-up Playwright case also passed on Chromium and WebKit.
  2. Full reset: PASS. Re-grant and device-fence mismatch request a full snapshot/reset. Reset preparation skips deleted/trashed notes with a recorded outcome; restore brings the note back into the recipient view. Red-first regressions now pass: regrant_full_snapshot_is_available_through_the_sharing_action, deleted_shared_notes_are_skipped_by_a_full_reset_and_return_after_restore, and full_reset_blocks_shared_writes_until_snapshot_acknowledgement.
  3. Credential revalidation on catch-up: PASS. Catch-up rechecks caller credential and scope before replay. A revoked app password closes the socket with the auth code; clients sign out and stop syncing. Red-first regressions now pass: live_revalidation_observes_app_password_revocation and revoked_app_password_is_closed_before_catch_up_replays_entries.

The owner room-delivery regression was also run red first: owner_room_delivery_does_not_repeat_auth_db_lookup observed 2 DB runs instead of 1. It now passes. Session/development owner deliveries check notes.read without opening a second auth DB round trip; app-password delivery keeps live DB credential validation.

Gates

  • PASS, dev-01: cargo fmt --all -- --check.
  • PASS, dev-01: cargo test --locked -j4 -p calternal-auth owner_unshared_admission_is_cached_until_a_grant_and_never_after and cargo test --locked -j4 -p calternal-auth owner_room_delivery_does_not_repeat_auth_db_lookup.
  • PASS, dev-01: cargo test --locked -j4 --no-fail-fast -p calternal-auth -p calternal-notes — 73 auth unit tests passed; package integration suites passed; Notes: 514 passed, 6 ignored.
  • PASS, dev-01: cargo test --locked -j4 -p calternal-server --test signout — 2 passed.
  • PASS, dev-01 Chromium: CI=1 CALTERNAL_BIN=/home/claude/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-chromium-1440 --workers=1 --retries=0 --grep=catches — setup plus catch-up case, 2/2.
  • PASS, dev-02 WebKit: CI=1 CALTERNAL_BIN=/home/cua/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-webkit-1440 --workers=1 --retries=0 --grep=catches — setup plus catch-up case, 2/2.

200-append performance

The same unoptimized dev/candidate binaries were built on dev-01 from dev 55f35fcde3bc91ca3781055577b93e93819f6cd4 and candidate 9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b, relayed through the control host, and SHA-256 verified on both perf VMs. Each VM ran 20 alternating pairs (40 runs), with a fresh Home and 1,000-line note per run; each run made 200 sequential acknowledged appends. Startup, creation, content verification and shutdown were outside the timed interval. All 200 markers appeared exactly once in every run.

VM dev median / p90 (s) candidate median / p90 (s) median delta
perf-01 0.725075 / 0.773837 0.726141 / 0.743474 +0.147%
perf-02 0.726562 / 0.779183 0.725857 / 0.752527 -0.097%

Both medians are within 1%. Binary SHA-256: dev e1fb7c00f4fe39c244aec6a5fcf68ad025ae7e871157ed2ab9c7387055adc3a9; candidate 7fb32a699060940dfaf49e03ae0f0e211d5323ae799ad907596ea71d8b01e4a5.

The sharing gates and stack rebase are complete. No PR was merged.

## Final sharing follow-up and stack rebase Verified at #876 head `9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b`. The stack is based on the live #827 head `76f11dc4ba06468a2a0ed4d4592cea6038206696`; requested commit `0543321b` is an ancestor. The single-authorization-under-the-lock behavior remains intact. | PR | Head | Base | |---|---|---| | #829 | `5cab44a4afdc932ce0afca65b61cb13137afe946` | #827 `76f11dc4ba06468a2a0ed4d4592cea6038206696` | | #835 | `3755691ddc63a740d36a4782f2163a3876f58cfc` | #829 `5cab44a4afdc932ce0afca65b61cb13137afe946` | | #842 | `0fb5e8ed3c161b70443b63c1649484fda758e5a6` | #835 `3755691ddc63a740d36a4782f2163a3876f58cfc` | | #876 | `9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b` | #842 `0fb5e8ed3c161b70443b63c1649484fda758e5a6` | ### Findings 1. **Fan-out drain: PASS.** The bounded durable worker drains committed intents, wakes after commit, and sweeps on startup/periodically. Per-recipient sequence/fence receipts make it idempotent and crash-resumable without holding the writer path. The Rust regressions were run red before the fixes and now pass: `committed_fanout_reaches_the_recipient_journal`, `fanout_record_once_recovers_after_publication_before_receipt`, and `fanout_delivery_order_uses_note_sequence_not_wall_clock`. The offline catch-up Playwright case also passed on Chromium and WebKit. 2. **Full reset: PASS.** Re-grant and device-fence mismatch request a full snapshot/reset. Reset preparation skips deleted/trashed notes with a recorded outcome; restore brings the note back into the recipient view. Red-first regressions now pass: `regrant_full_snapshot_is_available_through_the_sharing_action`, `deleted_shared_notes_are_skipped_by_a_full_reset_and_return_after_restore`, and `full_reset_blocks_shared_writes_until_snapshot_acknowledgement`. 3. **Credential revalidation on catch-up: PASS.** Catch-up rechecks caller credential and scope before replay. A revoked app password closes the socket with the auth code; clients sign out and stop syncing. Red-first regressions now pass: `live_revalidation_observes_app_password_revocation` and `revoked_app_password_is_closed_before_catch_up_replays_entries`. The owner room-delivery regression was also run red first: `owner_room_delivery_does_not_repeat_auth_db_lookup` observed 2 DB runs instead of 1. It now passes. Session/development owner deliveries check `notes.read` without opening a second auth DB round trip; app-password delivery keeps live DB credential validation. ### Gates - **PASS, dev-01:** `cargo fmt --all -- --check`. - **PASS, dev-01:** `cargo test --locked -j4 -p calternal-auth owner_unshared_admission_is_cached_until_a_grant_and_never_after` and `cargo test --locked -j4 -p calternal-auth owner_room_delivery_does_not_repeat_auth_db_lookup`. - **PASS, dev-01:** `cargo test --locked -j4 --no-fail-fast -p calternal-auth -p calternal-notes` — 73 auth unit tests passed; package integration suites passed; Notes: 514 passed, 6 ignored. - **PASS, dev-01:** `cargo test --locked -j4 -p calternal-server --test signout` — 2 passed. - **PASS, dev-01 Chromium:** `CI=1 CALTERNAL_BIN=/home/claude/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-chromium-1440 --workers=1 --retries=0 --grep=catches` — setup plus catch-up case, 2/2. - **PASS, dev-02 WebKit:** `CI=1 CALTERNAL_BIN=/home/cua/Developer/Worktrees/sharing-pr3-review/target/debug/calternal bunx playwright test e2e/admin-sharing.spec.ts --project=admin-webkit-1440 --workers=1 --retries=0 --grep=catches` — setup plus catch-up case, 2/2. ### 200-append performance The same unoptimized dev/candidate binaries were built on dev-01 from dev `55f35fcde3bc91ca3781055577b93e93819f6cd4` and candidate `9a98a7f18fcb0d24baa6b9143b4d7a98d527e50b`, relayed through the control host, and SHA-256 verified on both perf VMs. Each VM ran 20 alternating pairs (40 runs), with a fresh Home and 1,000-line note per run; each run made 200 sequential acknowledged appends. Startup, creation, content verification and shutdown were outside the timed interval. All 200 markers appeared exactly once in every run. | VM | dev median / p90 (s) | candidate median / p90 (s) | median delta | |---|---:|---:|---:| | perf-01 | 0.725075 / 0.773837 | 0.726141 / 0.743474 | +0.147% | | perf-02 | 0.726562 / 0.779183 | 0.725857 / 0.752527 | -0.097% | Both medians are within 1%. Binary SHA-256: dev `e1fb7c00f4fe39c244aec6a5fcf68ad025ae7e871157ed2ab9c7387055adc3a9`; candidate `7fb32a699060940dfaf49e03ae0f0e211d5323ae799ad907596ea71d8b01e4a5`. The sharing gates and stack rebase are complete. No PR was merged.
Author
Owner

Rebased onto the new #842 head 289b972c58c15233a47a61df5224688b3e473698 (see the resolution notes on #842). New head: bd7fcc6a3a861601dfa9cf896a2cc863f496d57b (was 9a98a7f1).

All 89 commits replayed with no conflicts. git diff origin/review/sharing-pr3-review HEAD -- crates is empty: server-side access checks, durable recovery and lifecycle code are unchanged; only the web side moves with #842's Style-stack wiring.

Gates ran on this head: same commands and results as on #842 (Chromium 299 passed 0 failed; WebKit reruns in isolation 248 and 11 passed, 0 failed, after load failures in the first combined run; see #842). The sharing specs (admin-sharing.spec.ts, 4 tests) pass on admin-chromium-1440/390 and admin-webkit-1440/390, including the follow-ups' offline-recovery tests. Not run: the full suite and Rust tests (train gates).

Rebased onto the new #842 head `289b972c58c15233a47a61df5224688b3e473698` (see the resolution notes on #842). New head: `bd7fcc6a3a861601dfa9cf896a2cc863f496d57b` (was `9a98a7f1`). All 89 commits replayed with no conflicts. `git diff origin/review/sharing-pr3-review HEAD -- crates` is empty: server-side access checks, durable recovery and lifecycle code are unchanged; only the web side moves with #842's Style-stack wiring. Gates ran on this head: same commands and results as on #842 (Chromium 299 passed 0 failed; WebKit reruns in isolation 248 and 11 passed, 0 failed, after load failures in the first combined run; see #842). The sharing specs (`admin-sharing.spec.ts`, 4 tests) pass on `admin-chromium-1440/390` and `admin-webkit-1440/390`, including the follow-ups' offline-recovery tests. Not run: the full suite and Rust tests (train gates).
Author
Owner

Restacked on #842 e38e9a52003ed2ee3349846e156f509414bb7b67: new head 9fec3d1f82bcd1414c06f88a9aca196cb1cb7743 (was bd7fcc6a). Only the base moved (the phone-bars limit change was reverted and the check fixed, see #842). crates/ is identical to before. phone-bars ran on this head: chromium-390 and webkit-390, --repeat-each=5, 1200 passed each, 0 failed.

Restacked on #842 `e38e9a52003ed2ee3349846e156f509414bb7b67`: new head `9fec3d1f82bcd1414c06f88a9aca196cb1cb7743` (was `bd7fcc6a`). Only the base moved (the phone-bars limit change was reverted and the check fixed, see #842). `crates/` is identical to before. `phone-bars` ran on this head: chromium-390 and webkit-390, `--repeat-each=5`, 1200 passed each, 0 failed.
Author
Owner

housekeeping #1009: needs-decision.

Depends on the blocked sharing UI #842. Offline rescue/lifecycle work remains valuable but must be restacked together with the sharing/access-check decision; do not enable cross-owner sharing through a housekeeping rebase.

original head: 9fec3d1f82bcd1414c06f88a9aca196cb1cb7743. against dev cd7f84432febfc63b788fbb9b9f9950452982d74, git cherry reports 108 unmatched and 0 equivalent patches. the landing audit lists each candidate missing commit and stable patch match in pr-876.md; unmatched patches can include superseded behavior.

left open, source branch unchanged. no rebase or new test claim for this decision lane.

housekeeping #1009: needs-decision. Depends on the blocked sharing UI #842. Offline rescue/lifecycle work remains valuable but must be restacked together with the sharing/access-check decision; do not enable cross-owner sharing through a housekeeping rebase. original head: `9fec3d1f82bcd1414c06f88a9aca196cb1cb7743`. against dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, git cherry reports 108 unmatched and 0 equivalent patches. the [landing audit](https://git.kayg.org/attachments/fe3c09dc-dea3-47a0-92e2-b44a777083e5) lists each candidate missing commit and stable patch match in `pr-876.md`; unmatched patches can include superseded behavior. left open, source branch unchanged. no rebase or new test claim for this decision lane.
Author
Owner

Gates on the restack, head db6a92981

gate where result
cargo clippy --workspace --all-targets --locked -j4 -- -D warnings dev-01 clean (after an allow(too_many_arguments) on the journal push helper, documented)
cargo test … -p calternal-core -p calternal-auth -p calternal-notes -p calternal-server -p calternal -p calternal-attachments -p calternal-daily --no-fail-fast dev-01 0 failed
contract UPDATE_CONTRACTS=1 … contract dev-01 regenerated (addad3abe): schemars now writes the nullable reset input as type: [object, null]
bun run --cwd packages/editor test dev-01 562/562
bun run --cwd packages/editor check dev-01 only old dev's picker-list.ts errors (fixed on dev by #1024)
svelte-check dev-01 only old dev's 4 (InsertSheet, TaskCard)
bun run --cwd apps/web test dev-01 pass
admin-sharing.spec --repeat-each=2 (incl. "offline edits are rescued before revocation…", "delete and restore reach recipients") admin-chromium-1440 / 390 9/9, 9/9
same admin-webkit-1440 / 390 9/9, 9/9 (and 13/13 at --repeat-each=3)
first paint dev-01 perf/web.sh --bundle-only 223.902 KB vs #842 222.995 (+0.91 KB), approved by kayg 11-09, offset by the credits PR; lines set in perf/budgets.json
perf ABAB ×5 vs dev + control perf-01 queued (perf-turn sharing-876); binary 6f190ea7… is staged

Spec fix on the restack: the phone list is virtualized, so the own-note row is checked on wide screens; phones check the same copy by its address. The "Add people" fill already searched by full name on this branch.

## Gates on the restack, head `db6a92981` | gate | where | result | |---|---|---| | `cargo clippy --workspace --all-targets --locked -j4 -- -D warnings` | dev-01 | clean (after an `allow(too_many_arguments)` on the journal push helper, documented) | | `cargo test … -p calternal-core -p calternal-auth -p calternal-notes -p calternal-server -p calternal -p calternal-attachments -p calternal-daily --no-fail-fast` | dev-01 | 0 failed | | contract `UPDATE_CONTRACTS=1 … contract` | dev-01 | regenerated (`addad3abe`): schemars now writes the nullable reset input as `type: [object, null]` | | `bun run --cwd packages/editor test` | dev-01 | 562/562 | | `bun run --cwd packages/editor check` | dev-01 | only old dev's `picker-list.ts` errors (fixed on dev by #1024) | | `svelte-check` | dev-01 | only old dev's 4 (InsertSheet, TaskCard) | | `bun run --cwd apps/web test` | dev-01 | pass | | `admin-sharing.spec --repeat-each=2` (incl. "offline edits are rescued before revocation…", "delete and restore reach recipients") | admin-chromium-1440 / 390 | 9/9, 9/9 | | same | admin-webkit-1440 / 390 | 9/9, 9/9 (and 13/13 at `--repeat-each=3`) | | first paint | dev-01 `perf/web.sh --bundle-only` | 223.902 KB vs #842 222.995 (+0.91 KB), approved by kayg 11-09, offset by the credits PR; lines set in `perf/budgets.json` | | perf ABAB ×5 vs dev + control | perf-01 | queued (perf-turn `sharing-876`); binary `6f190ea7…` is staged | Spec fix on the restack: the phone list is virtualized, so the own-note row is checked on wide screens; phones check the same copy by its address. The "Add people" fill already searched by full name on this branch.
kayg changed target branch from feat/sharing-s1-pr3 to dev 2026-11-09 13:47:22 +00:00
Author
Owner

perf-VM ABAB ×5: #876 (restack head db6a92981, which contains #842 and #909) vs dev 661123e94, with a byte-identical dev control

Setup:

  • perf-turn sharing-876; perf-01 + perf-02 held together, measured on perf-01, 11-09 18:54–20:13 IST.
  • calternal-perf from dev for every variant: --sizes 10k,100k --runs 4 --cold-runs 0 --idle-secs 0 --seed 1, 5 rotating sessions.
  • Binaries: dev = ctl f66e5c90…, head 6f190ea7…. Later commits are merges with dev (#842, #1033), spec, format and budget changes; the server code on the write path is the same.
size metric stat dev head876 ctl (control) head876 vs dev control vs dev verdict
10k sync.save_ack p50 3.741 [3.644-3.812] 3.688 [3.641-4.525] 3.675 [3.613-3.688] -1.4% -1.8% flat
10k sync.save_ack p95 5.292 [4.721-9.655] 5.270 [4.750-9.424] 5.365 [4.650-6.386] -0.4% +1.4% flat
10k sync.durable_write p50 25.006 [24.405-28.454] 24.801 [24.663-29.099] 24.659 [24.259-24.959] -0.8% -1.4% flat
10k sync.durable_write p95 35.019 [28.882-49.970] 33.318 [29.029-46.048] 29.957 [28.877-33.285] -4.9% -14.5% flat
10k sync.round_trip p50 0.219 [0.205-0.251] 0.204 [0.199-0.246] 0.208 [0.201-0.213] -7.0% -5.3% flat
10k sync.round_trip p95 0.362 [0.345-0.385] 0.329 [0.320-0.397] 0.339 [0.327-0.346] -8.9% -6.3% flat
10k storm.typing p50 2.801 [2.757-3.150] 2.779 [2.771-3.323] 2.802 [2.755-2.894] -0.8% +0.0% flat
10k storm.typing p95 2.890 [2.773-3.662] 2.851 [2.814-3.711] 2.867 [2.826-4.730] -1.3% -0.8% flat
10k storm.save_ack p50 7.396 [7.305-7.928] 7.326 [7.280-8.173] 7.294 [7.221-7.554] -1.0% -1.4% flat
10k storm.save_ack p95 14.238 [13.611-19.164] 14.888 [14.387-20.578] 14.605 [14.441-15.220] +4.6% +2.6% flat
10k search.query p50 1.484 [1.394-1.545] 1.533 [1.475-1.665] 1.541 [1.511-1.635] +3.2% +3.8% flat
10k search.query p95 4.163 [3.887-4.212] 4.051 [3.982-4.804] 4.037 [3.881-4.418] -2.7% -3.0% flat
10k search.first_query p50 3.792 [3.553-4.314] 4.285 [3.863-4.820] 4.254 [3.758-5.021] +13.0% +12.2% flat
10k search.first_query p95 26.463 [26.063-90.198] 29.418 [26.598-50.614] 26.502 [25.888-30.668] +11.2% +0.1% flat
10k open.note p50 0.712 [0.667-0.748] 0.705 [0.675-0.795] 0.683 [0.675-0.726] -1.1% -4.1% flat
10k open.note p95 1.250 [1.212-2.857] 1.465 [1.226-1.520] 1.365 [1.225-1.461] +17.3% +9.2% flat
10k open.api_get p50 4.066 [3.871-4.119] 3.959 [3.847-4.831] 3.896 [3.842-3.936] -2.6% -4.2% flat
10k open.api_get p95 10.462 [7.275-13.377] 8.487 [7.115-13.072] 7.796 [7.502-8.886] -18.9% -25.5% flat
10k startup.healthz p50 4.921 [4.780-4.993] 4.901 [4.839-4.933] 4.857 [4.822-6.032] -0.4% -1.3% flat
10k startup.healthz p95 6.017 [5.178-8.462] 5.902 [5.092-7.099] 7.125 [4.926-7.880] -1.9% +18.4% flat
10k home.first_page p50 2.262 [2.153-2.566] 2.406 [2.197-2.686] 2.188 [2.061-3.041] +6.4% -3.2% flat
10k home.first_page p95 3.968 [2.564-7.839] 5.624 [2.841-6.523] 3.039 [2.343-4.849] +41.7% -23.4% flat
10k journal.catch_up p50 29.478 [27.666-31.010] 28.258 [27.888-30.635] 27.614 [26.996-28.460] -4.1% -6.3% flat
10k journal.catch_up p95 31.155 [29.613-40.902] 32.672 [28.982-38.476] 30.210 [28.814-47.289] +4.9% -3.0% flat
100k sync.save_ack p50 3.761 [3.664-4.106] 3.731 [3.566-3.863] 3.702 [3.628-3.768] -0.8% -1.6% flat
100k sync.save_ack p95 8.741 [5.031-10.444] 4.918 [4.703-9.662] 5.132 [4.820-8.762] -43.7% -41.3% flat
100k sync.durable_write p50 25.132 [24.945-25.680] 24.581 [24.288-26.666] 24.724 [24.134-24.757] -2.2% -1.6% flat
100k sync.durable_write p95 34.747 [29.390-48.221] 30.234 [27.976-45.492] 30.477 [29.581-33.925] -13.0% -12.3% flat
100k sync.round_trip p50 0.245 [0.213-0.275] 0.219 [0.201-0.275] 0.216 [0.197-0.243] -10.8% -12.0% flat
100k sync.round_trip p95 0.383 [0.345-0.557] 0.345 [0.333-0.450] 0.351 [0.316-0.390] -10.1% -8.4% flat
100k storm.typing p50 3.208 [2.735-3.619] 2.778 [2.736-3.397] 2.790 [2.783-3.211] -13.4% -13.0% flat
100k storm.typing p95 3.740 [2.865-5.983] 2.893 [2.819-3.867] 2.917 [2.856-3.514] -22.7% -22.0% flat
100k storm.save_ack p50 7.723 [7.417-9.479] 7.384 [7.289-7.908] 7.406 [7.233-7.950] -4.4% -4.1% flat
100k storm.save_ack p95 19.192 [13.365-21.693] 13.654 [13.486-20.386] 14.833 [13.037-19.165] -28.9% -22.7% flat
100k search.query p50 1.733 [1.580-1.829] 1.714 [1.624-1.774] 1.641 [1.588-1.696] -1.1% -5.3% flat
100k search.query p95 3.798 [3.650-4.279] 3.865 [3.656-3.992] 3.848 [3.698-3.960] +1.8% +1.3% flat
100k search.first_query p50 4.056 [3.714-4.434] 3.884 [3.632-6.550] 4.346 [3.688-5.641] -4.2% +7.1% flat
100k search.first_query p95 26.728 [26.325-28.179] 27.385 [27.144-42.926] 26.790 [25.841-35.018] +2.5% +0.2% flat
100k open.note p50 1.094 [0.902-1.316] 0.988 [0.959-1.102] 1.099 [1.013-1.263] -9.6% +0.5% flat
100k open.note p95 3.877 [2.725-5.843] 3.303 [3.083-3.867] 3.091 [2.670-3.733] -14.8% -20.3% flat
100k open.api_get p50 4.159 [4.083-4.468] 4.201 [4.130-5.246] 4.192 [4.142-6.229] +1.0% +0.8% flat
100k open.api_get p95 12.282 [7.596-13.124] 10.564 [7.799-13.968] 11.338 [7.534-12.624] -14.0% -7.7% flat
100k startup.healthz p50 4.981 [4.849-5.030] 4.954 [4.828-5.081] 4.906 [4.844-5.321] -0.5% -1.5% flat
100k startup.healthz p95 8.496 [7.208-9.434] 5.924 [5.412-7.216] 8.141 [7.167-10.112] -30.3% -4.2% flat
100k home.first_page p50 3.294 [2.982-3.363] 2.794 [2.383-3.106] 3.268 [2.705-3.693] -15.2% -0.8% flat
100k home.first_page p95 6.765 [3.629-10.699] 3.836 [3.593-8.282] 3.828 [3.025-7.971] -43.3% -43.4% flat
100k journal.catch_up p50 283.953 [268.288-305.302] 273.553 [267.678-303.679] 276.235 [269.484-286.634] -3.7% -2.7% flat
100k journal.catch_up p95 308.065 [287.720-362.903] 302.035 [296.078-342.517] 289.603 [281.733-335.528] -2.0% -6.0% flat

Reading: flat everywhere, with no row outside the control. This PR changes the write path: per-Installation control gates on writes, and durable fan-out intents drained after commit. Saves (sync.save_ack, sync.durable_write), typing (storm.*), the room round trip and the journal are equal or better than dev, and the control moves the same way.

## perf-VM ABAB ×5: #876 (restack head `db6a92981`, which contains #842 and #909) vs dev `661123e94`, with a byte-identical dev control Setup: - perf-turn `sharing-876`; perf-01 + perf-02 held together, measured on perf-01, 11-09 18:54–20:13 IST. - `calternal-perf` from dev for every variant: `--sizes 10k,100k --runs 4 --cold-runs 0 --idle-secs 0 --seed 1`, 5 rotating sessions. - Binaries: dev = ctl `f66e5c90…`, head `6f190ea7…`. Later commits are merges with dev (#842, #1033), spec, format and budget changes; the server code on the write path is the same. | size | metric | stat | dev | head876 | ctl (control) | head876 vs dev | control vs dev | verdict | |---|---|---|---|---|---|---|---|---| | 10k | sync.save_ack | p50 | 3.741 [3.644-3.812] | 3.688 [3.641-4.525] | 3.675 [3.613-3.688] | -1.4% | -1.8% | flat | | 10k | sync.save_ack | p95 | 5.292 [4.721-9.655] | 5.270 [4.750-9.424] | 5.365 [4.650-6.386] | -0.4% | +1.4% | flat | | 10k | sync.durable_write | p50 | 25.006 [24.405-28.454] | 24.801 [24.663-29.099] | 24.659 [24.259-24.959] | -0.8% | -1.4% | flat | | 10k | sync.durable_write | p95 | 35.019 [28.882-49.970] | 33.318 [29.029-46.048] | 29.957 [28.877-33.285] | -4.9% | -14.5% | flat | | 10k | sync.round_trip | p50 | 0.219 [0.205-0.251] | 0.204 [0.199-0.246] | 0.208 [0.201-0.213] | -7.0% | -5.3% | flat | | 10k | sync.round_trip | p95 | 0.362 [0.345-0.385] | 0.329 [0.320-0.397] | 0.339 [0.327-0.346] | -8.9% | -6.3% | flat | | 10k | storm.typing | p50 | 2.801 [2.757-3.150] | 2.779 [2.771-3.323] | 2.802 [2.755-2.894] | -0.8% | +0.0% | flat | | 10k | storm.typing | p95 | 2.890 [2.773-3.662] | 2.851 [2.814-3.711] | 2.867 [2.826-4.730] | -1.3% | -0.8% | flat | | 10k | storm.save_ack | p50 | 7.396 [7.305-7.928] | 7.326 [7.280-8.173] | 7.294 [7.221-7.554] | -1.0% | -1.4% | flat | | 10k | storm.save_ack | p95 | 14.238 [13.611-19.164] | 14.888 [14.387-20.578] | 14.605 [14.441-15.220] | +4.6% | +2.6% | flat | | 10k | search.query | p50 | 1.484 [1.394-1.545] | 1.533 [1.475-1.665] | 1.541 [1.511-1.635] | +3.2% | +3.8% | flat | | 10k | search.query | p95 | 4.163 [3.887-4.212] | 4.051 [3.982-4.804] | 4.037 [3.881-4.418] | -2.7% | -3.0% | flat | | 10k | search.first_query | p50 | 3.792 [3.553-4.314] | 4.285 [3.863-4.820] | 4.254 [3.758-5.021] | +13.0% | +12.2% | flat | | 10k | search.first_query | p95 | 26.463 [26.063-90.198] | 29.418 [26.598-50.614] | 26.502 [25.888-30.668] | +11.2% | +0.1% | flat | | 10k | open.note | p50 | 0.712 [0.667-0.748] | 0.705 [0.675-0.795] | 0.683 [0.675-0.726] | -1.1% | -4.1% | flat | | 10k | open.note | p95 | 1.250 [1.212-2.857] | 1.465 [1.226-1.520] | 1.365 [1.225-1.461] | +17.3% | +9.2% | flat | | 10k | open.api_get | p50 | 4.066 [3.871-4.119] | 3.959 [3.847-4.831] | 3.896 [3.842-3.936] | -2.6% | -4.2% | flat | | 10k | open.api_get | p95 | 10.462 [7.275-13.377] | 8.487 [7.115-13.072] | 7.796 [7.502-8.886] | -18.9% | -25.5% | flat | | 10k | startup.healthz | p50 | 4.921 [4.780-4.993] | 4.901 [4.839-4.933] | 4.857 [4.822-6.032] | -0.4% | -1.3% | flat | | 10k | startup.healthz | p95 | 6.017 [5.178-8.462] | 5.902 [5.092-7.099] | 7.125 [4.926-7.880] | -1.9% | +18.4% | flat | | 10k | home.first_page | p50 | 2.262 [2.153-2.566] | 2.406 [2.197-2.686] | 2.188 [2.061-3.041] | +6.4% | -3.2% | flat | | 10k | home.first_page | p95 | 3.968 [2.564-7.839] | 5.624 [2.841-6.523] | 3.039 [2.343-4.849] | +41.7% | -23.4% | flat | | 10k | journal.catch_up | p50 | 29.478 [27.666-31.010] | 28.258 [27.888-30.635] | 27.614 [26.996-28.460] | -4.1% | -6.3% | flat | | 10k | journal.catch_up | p95 | 31.155 [29.613-40.902] | 32.672 [28.982-38.476] | 30.210 [28.814-47.289] | +4.9% | -3.0% | flat | | 100k | sync.save_ack | p50 | 3.761 [3.664-4.106] | 3.731 [3.566-3.863] | 3.702 [3.628-3.768] | -0.8% | -1.6% | flat | | 100k | sync.save_ack | p95 | 8.741 [5.031-10.444] | 4.918 [4.703-9.662] | 5.132 [4.820-8.762] | -43.7% | -41.3% | flat | | 100k | sync.durable_write | p50 | 25.132 [24.945-25.680] | 24.581 [24.288-26.666] | 24.724 [24.134-24.757] | -2.2% | -1.6% | flat | | 100k | sync.durable_write | p95 | 34.747 [29.390-48.221] | 30.234 [27.976-45.492] | 30.477 [29.581-33.925] | -13.0% | -12.3% | flat | | 100k | sync.round_trip | p50 | 0.245 [0.213-0.275] | 0.219 [0.201-0.275] | 0.216 [0.197-0.243] | -10.8% | -12.0% | flat | | 100k | sync.round_trip | p95 | 0.383 [0.345-0.557] | 0.345 [0.333-0.450] | 0.351 [0.316-0.390] | -10.1% | -8.4% | flat | | 100k | storm.typing | p50 | 3.208 [2.735-3.619] | 2.778 [2.736-3.397] | 2.790 [2.783-3.211] | -13.4% | -13.0% | flat | | 100k | storm.typing | p95 | 3.740 [2.865-5.983] | 2.893 [2.819-3.867] | 2.917 [2.856-3.514] | -22.7% | -22.0% | flat | | 100k | storm.save_ack | p50 | 7.723 [7.417-9.479] | 7.384 [7.289-7.908] | 7.406 [7.233-7.950] | -4.4% | -4.1% | flat | | 100k | storm.save_ack | p95 | 19.192 [13.365-21.693] | 13.654 [13.486-20.386] | 14.833 [13.037-19.165] | -28.9% | -22.7% | flat | | 100k | search.query | p50 | 1.733 [1.580-1.829] | 1.714 [1.624-1.774] | 1.641 [1.588-1.696] | -1.1% | -5.3% | flat | | 100k | search.query | p95 | 3.798 [3.650-4.279] | 3.865 [3.656-3.992] | 3.848 [3.698-3.960] | +1.8% | +1.3% | flat | | 100k | search.first_query | p50 | 4.056 [3.714-4.434] | 3.884 [3.632-6.550] | 4.346 [3.688-5.641] | -4.2% | +7.1% | flat | | 100k | search.first_query | p95 | 26.728 [26.325-28.179] | 27.385 [27.144-42.926] | 26.790 [25.841-35.018] | +2.5% | +0.2% | flat | | 100k | open.note | p50 | 1.094 [0.902-1.316] | 0.988 [0.959-1.102] | 1.099 [1.013-1.263] | -9.6% | +0.5% | flat | | 100k | open.note | p95 | 3.877 [2.725-5.843] | 3.303 [3.083-3.867] | 3.091 [2.670-3.733] | -14.8% | -20.3% | flat | | 100k | open.api_get | p50 | 4.159 [4.083-4.468] | 4.201 [4.130-5.246] | 4.192 [4.142-6.229] | +1.0% | +0.8% | flat | | 100k | open.api_get | p95 | 12.282 [7.596-13.124] | 10.564 [7.799-13.968] | 11.338 [7.534-12.624] | -14.0% | -7.7% | flat | | 100k | startup.healthz | p50 | 4.981 [4.849-5.030] | 4.954 [4.828-5.081] | 4.906 [4.844-5.321] | -0.5% | -1.5% | flat | | 100k | startup.healthz | p95 | 8.496 [7.208-9.434] | 5.924 [5.412-7.216] | 8.141 [7.167-10.112] | -30.3% | -4.2% | flat | | 100k | home.first_page | p50 | 3.294 [2.982-3.363] | 2.794 [2.383-3.106] | 3.268 [2.705-3.693] | -15.2% | -0.8% | flat | | 100k | home.first_page | p95 | 6.765 [3.629-10.699] | 3.836 [3.593-8.282] | 3.828 [3.025-7.971] | -43.3% | -43.4% | flat | | 100k | journal.catch_up | p50 | 283.953 [268.288-305.302] | 273.553 [267.678-303.679] | 276.235 [269.484-286.634] | -3.7% | -2.7% | flat | | 100k | journal.catch_up | p95 | 308.065 [287.720-362.903] | 302.035 [296.078-342.517] | 289.603 [281.733-335.528] | -2.0% | -6.0% | flat | **Reading: flat everywhere, with no row outside the control.** This PR changes the write path: per-Installation control gates on writes, and durable fan-out intents drained after commit. Saves (`sync.save_ack`, `sync.durable_write`), typing (`storm.*`), the room round trip and the journal are equal or better than dev, and the control moves the same way.
Author
Owner

READY FOR MERGE: head 6e28e5947 (merged with dev d0bf2838c, which has #842 and #1036; the tree is the same as the gated 5f68c1b78)

gate result
cargo fmt --check / clippy -D warnings / Rust tests (core, auth, notes, server, calternal, attachments, daily) clean / clean / 0 failed
editor tests / editor check 562/562 / pass
svelte-check / web unit / action-types --check 0 errors / pass / ok
bundle gate pass. first_js_brotli 223.653 KB vs dev d0bf2838c 222.960 (+0.69 KB; kayg approved up to +0.91 on 11-09, offset by the credits PR). idle-screens 56.44
lazy-offline.spec chromium-1440 ×3 0/3 failed (the #842 red is fixed by #1036)
admin-sharing + admin + preload + lazy-ui + lazy-offline + glass + shortcuts + switching + list-chrome + list-fit + sidebar-edge + pane-transition + note-pills + note-bars + tabs chromium-1440 50 passed · chromium-390 66 passed · webkit-1440 48 passed · webkit-390 64 passed: 0 failed
admin projects (same set) admin-chromium-1440 12 · admin-chromium-390 12 · admin-webkit-1440 12 passed · admin-webkit-390 11 passed + 1 first-run failure (invites sheet). That one passes --repeat-each=10 on the branch and on dev (11/11 each) → flaky rule, #1037 opened
perf ABAB ×5 vs dev + control flat, no row outside the control (#issuecomment-88906)

🤖 Generated with Claude Code

## READY FOR MERGE: head `6e28e5947` (merged with dev `d0bf2838c`, which has #842 and #1036; the tree is the same as the gated `5f68c1b78`) | gate | result | |---|---| | `cargo fmt --check` / clippy `-D warnings` / Rust tests (core, auth, notes, server, calternal, attachments, daily) | clean / clean / 0 failed | | editor tests / editor check | 562/562 / pass | | svelte-check / web unit / `action-types --check` | 0 errors / pass / ok | | bundle gate | pass. first_js_brotli **223.653 KB** vs dev `d0bf2838c` 222.960 (+0.69 KB; kayg approved up to +0.91 on 11-09, offset by the credits PR). idle-screens 56.44 | | `lazy-offline.spec` chromium-1440 ×3 | 0/3 failed (the #842 red is fixed by #1036) | | admin-sharing + admin + preload + lazy-ui + lazy-offline + glass + shortcuts + switching + list-chrome + list-fit + sidebar-edge + pane-transition + note-pills + note-bars + tabs | chromium-1440 50 passed · chromium-390 66 passed · webkit-1440 48 passed · webkit-390 64 passed: **0 failed** | | admin projects (same set) | admin-chromium-1440 12 · admin-chromium-390 12 · admin-webkit-1440 12 passed · admin-webkit-390 11 passed + 1 first-run failure (invites sheet). That one passes `--repeat-each=10` on the branch and on dev (11/11 each) → flaky rule, **#1037** opened | | perf ABAB ×5 vs dev + control | flat, no row outside the control ([#issuecomment-88906](https://git.kayg.org/kayg/calternal/pulls/876#issuecomment-88906)) | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Merged into dev locally (head 6e28e5947 is in origin/dev); closing.

Merged into dev locally (head `6e28e5947` is in origin/dev); closing.
kayg closed this pull request 2026-11-09 18:55:07 +00:00
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
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!876
No description provided.