Closed
kayg
wants to merge 12 commits from
shared/restore-and-stale-918-919 into dev
pull from: shared/restore-and-stale-918-919
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
kayg:feat/contrast
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:feat/files-s1-identity
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/smooth-pane-transitions
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:feat/tasks-delete-promote-duplicate
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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!928
Loading…
Reference in a new issue
No description provided.
Delete branch "shared/restore-and-stale-918-919"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Simplest design that meets #918/#919 (updated 2026-11-09)
Evidence and open measurements
Comment #86388 measured candidate
95af4df2:startup_1m_file_evictedwas +35.75% (+0.188 ms) against a −2.59% control. Cause:restored_at_openvalidated a same-epoch witness behind the database head and then calledwrite_now, which rewrote and synced the witness during open. Commitf71379a1cleaves a valid older mark durable and has the existing idle/staleness worker advance it. First-use initialization still makes its witness durable before setting the initialized flag or returning signed tokens, and epoch rotation still waits for its witness before publishing the new epoch. At merged head8357e925a(dev2fa9e18fb), comments 88632, 88778 and the room-rerun comment showstartup_1m_file_evictedflat against its control at 20 obs/arm, with commit, position and catch-up faster. Open: small p50 shifts instartup_healthz(first-install key and witness creation),sync_round_tripandsave_ack(per-entry signing), and a higher soak max. These need an owner decision under the no-regression rule.Design
Relaxedatomic store. It does not read a clock, inspect dirty age, wake the flusher, stat a path, or write the witness. The flush task polls that head at no more than 100 ms intervals and records changes itself, so direct commits are covered without save-path coordination.pwriteand callsfdatasync. Two alternating checksummed records carry sequence numbers so a torn write leaves the prior slot available. First initialization writes a valid slot before syncing the parent directory; the steady path does not rename a temporary file or sync the directory. A fresh open adds one open/read/close per flush, at most once per 30 seconds during continuous saves; that bounded metadata and descriptor work is negligible beside the sync and stays off the save and commit paths.EventRecorder::save_scopefrom the engine for an already-open store and never open one. Flush after one quiet second with no save in flight, or when the oldest unflushed high-water reaches the 30-second maximum age and no save is in flight: the save's end wakes the flusher, so the witness sync lands in the gap before the next save. A save stream that never pauses forces the flush at twice the maximum age (60 seconds). Also flush after the writer drains at clean shutdown. A crash can leave up to 30 seconds, plus the save in flight, of committed journal tail newer than the external witness (60 seconds worst case); a later signed cursor beyond the restored head, at a missing entry, or at a reused entry triggers the safe restore check. Storage failures extend the window until retry succeeds.epoch-seq-c-m), so a flush opens SQLite only when every cache misses. The commit path is unchanged./healthz. The store is not returned until both are durable, so no signed token can go out earlier.(dev, ino)and a linked descriptor before writing and again after sync. If the configured path was replaced, the worker repairs the last durable mark through the slow atomic rewrite, returnsInternal, and publishes no epoch until the witness is durable at the configured path. Identity checks stay on the flush worker; a fresh open does not remove the race betweenopen()andfdatasync().Failure table
a_crash_mid_flush_keeps_the_previous_valid_slota_torn_slot_falls_back_to_the_previous_valid_slotboth_invalid_highwater_slots_trigger_the_safe_restore_checknlink > 0Internal, and retry; never publish an epoch from the stale inodeopening_a_lagging_witness_does_not_sync_during_startupplus idle, maximum-age, and shutdown testsmaximum_age_flush_waits_for_the_save_in_flight,a_save_stream_that_never_pauses_forces_the_flush_at_twice_the_maximum_age,maximum_age_flush_waits_for_the_save_in_flight_to_end,maximum_staleness_flushes_during_a_continuous_save_streamGates
Run
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo test --workspace --no-fail-fast. Run save/sync Playwright specs on Chromium and WebKit at 1440 and 390. With both perf locks held, interleave dev controls and candidate arms in ABAB order for the signed commit bisect (×3 per stage), then the no-idle full matrix (ABAB ×5; startup rows at least 20 samples per arm) and a three-minute continuous-save soak at 200 ms cadence. Report every path, includingnotes_savep95 and p99. Every metric must be flat or better beyond the control spread. No merge.Implements #918 and #919 as approved in the Mail plan v4 review (#920, comments 77534/77555/77574/77580), plus the durability note on #918 (77762). Shared machinery only: no mail code. Notes behaviour is unchanged (no notes action opts in;
stale_patch_keeps_newer_typingproves a stale patch still merges).#918: restore detection
epoch-seq-c-m.c= 32 bits of blake3 over the entry atseq(seq ‖ at_ms ‖ kind ‖ item ‖ request_id);m= 32 bits keyed with<secrets>/users/<u>/token.key(0600, outside backups). Written for every entry in the writer and every read (since), plustoken_at()for ready/replay/empty-page tokens.UserStore::position(now async): badm→ "invalid token", never a rotation; other epoch → expired; validmwithseq > head, a missing entry or a differentc→ proof: rotate, then expired. Plainepoch-seqtokens still work while not past the head. A device at the head costs no read (cachedcof the newest entry); otherwise one primary-key read.<secrets>/users/<u>/headis a preallocated one-page file with two alternating checksummed slots. It is flushed after a one-second save quiet window, at 30 seconds maximum age, and after the writer drains at clean shutdown. Steady writes use one inactive-slotpwriteandfdatasync; startup reads one bounded page.meta.highwater=1→ restored; missing, no flag → first deploy (file + flag, nobody rotates); other epoch, or same epoch with file ahead of head orc≠ entry → restored.Cmd::Rotate(observed_epoch), so concurrent proofs rotate once):epoch,epoch_head, andhighwaterin one FULL commit, then the new witness slot is made durable, then the new epoch is published. Ordinary commits stay NORMAL; no fsync is added to the journal writer.Engine::restored(user)returns awatchcounter (>0 at start = restored),Engine::epoch_head(user),ActionCtx::journal_info(). Live sockets end withsync_token_expiredon rotation so no session keeps issuing new-epoch tokens to a client that has not re-read.--secrets DIR/CALTERNAL_SECRETSonserve. Without it: plain tokens, no detection, one warning at start (tests and one-off tools). Deploy needs the flag set to a folder outside the backup set (docs/backup.mdupdated).sync_token_expiredwith that detail (clients already re-read on it); no new error code, so contracts do not change.topiccolumn/index on the save log. Nothing here needs it; it stays with the sync-topics/mail PR, so there is no migration to time. Cold-start/open cost at 1M entries is measured instead.#919: opt-in per-field check
calternal_core::stale:Guard {base, after}(flatten into an action input),Tracked<V>(value, epoch, seq, req;setstamps only on a real change;rebuilttakes the head;with_pendingfolds accepted-but-unapplied actions),check_field→Apply | NoChange | Stale{epoch_only}.baseis given, the field changed after it, and the last change's op is not inafter. A base from an older epoch is stale withepoch_only; marks stamped before a rotation count asepoch_head.field.seq > base.seq). The issue text says≥, but a token already includes the entry at its seq, and≥would make a rebuilt row (seq = head) refuse the replay of a device that re-read at that very head, forever.apps/web/src/lib/action-outbox.ts: merges only unsent ops per (target, field); merged op keeps the oldest base; a merge back to the original is dropped only when nothing was sent;after();restamp(token)re-stamps and re-queues every unconfirmed op after a rotation;replay:falseops (delete forever) are handed back, never replayed. No existing screen uses it yet (prefs keep their own outbox).Idempotency after a power cut (77762)
Idempotency records in
index.dbare NORMAL, so an action that replied can run again after a power cut. No fsync added to the shared writer. Audit of every plugin with mutating actions:history.db(FULL)history.db)history.db(jobs::attempt)ON CONFLICT DO NOTHINGinauth.db(FULL); the reply carries only the idTests
store/src/tests/restore.rs: signed tokens everywhere; garbled/hand-edited token (invalid, no rotation); other-epoch token; first deploy; backup after the last start; backup from before an earlier rotation; host snapshot rollback (found by the first newer token); reused seq with a different entry; lost log tail at open; every file back to its last fsync, head passes the device (77762); zero-length/torn file; ahead file; rotation durable before new tokens + power loss; two proofs rotate once; idle/max-age/shutdown flush; torn-slot fallback; both-slots-bad safe restore; key mode.core/src/stale/tests.rs+engine/src/tests/restore.rs: flag racing read, two devices mark read, lost reply then opposite toggle, stale base racing a pending action, two tabs + retry after the prune, rebuild then late retry, rebuild then unchanged value, online adapter without base, restore →stale_epoch→ re-stamp → applies.apps/web/src/lib/action-outbox.test.ts.Gate and perf tables follow as comments.
🤖 Generated with Claude Code
resuming test/perf qualification. dev merged and pushed in
fa1253fb; current test headbb260905. no merge will be performed.coverage audit: existing tests cover first deploy (no rotation), backup after start, backup before rotation, snapshot rollback, reused seq, lost WAL tail, garbled tokens without rotation, torn files, concurrent proofs, timer, and durable rotation. #919 tests cover independent fields, equal values, own unconfirmed ops, pending actions, two tabs/late retry, rebuild, old epoch replay, and unchanged notes merging. added tests for a missing entry below head, a same-epoch witness with changed entry, a deleted initialized witness, and failed witness rewrite.
the last added test targets an uncovered requirement: restore::rotate publishes the epoch even if highwater::write returns an error. red verification is queued through slot on dev-01. i will report its actual result. also corrected the existing outbox replay-id assertion to snapshot the old ids before restamp mutates the objects.
scope discrepancy: the approved mail plan §2/§18 includes sync topics, topic SQL index and refill collapse in P0b, but this PR explicitly defers them. LiveQuery currently accepts only since; schema has no topic column/index. a “sync with topics” perf pass cannot be claimed against this implementation. it will be reported as missing, alongside measured current catch-up costs.
workspace gates and Chromium are queued on dev-01 under Nix/slot; WebKit build/specs are queued on dev-02. the active train is respected. results and exact commands will follow.
additional coverage gap: Engine::execute returns store.head().to_string() for read-only actions, so a non-empty snapshot/list reply still carries an unsigned token even with --secrets enabled. added read_only_action_replies_carry_signed_tokens in
d117982bto require the entry proof; its first execution is queued. this matters because the snapshot token is the device’s reconnect cursor.qualification is waiting behind the live bm14e train locks on both dev hosts. queued jobs: dev-01 p0b-final-chromium (workspace gates, store regression, outbox, signed-server Chromium); dev-02 p0b-final-webkit (signed-server WebKit); dev-01 p0b-release (candidate/dev release servers and disposable probe). all heavy commands use Nix and ~/bin/slot, four cargo jobs and one Playwright worker. no perf locks taken while builds wait.
red verification results on
b4a45605(format-only follow-upac4f6f29):a_failed_highwater_rewrite_does_not_publish_the_new_epoch: fails because the published epoch changes despite the witness write returning an internal error. this violates the required publish-after-durable-witness ordering.a_failed_first_deploy_can_retry_without_rotating: fails because the first failed witness write leaves the highwater flag set; retry rotates an existing user. this violates first-deploy initialization without rotation.read_only_action_replies_carry_signed_tokens: fails with 2 token parts instead of 4. snapshot replies omit the entry proof.fresh clippy rejects
std::fs::renameinhighwater.rs:54andtoken.rs:44under the repository’s disallowed-methods rule. no lint was suppressed. rustfmt’s reported changes are pushed inac4f6f29; its rerun follows after active jobs settle.workspace tests, browser gates and comparative builds are still underway. no merge, no perf pass claimed.
qualification checkpoint, 13:27 ist. current head
dd16a8c93a644f49df1b633bef91f491c2ff20cb; dev merged at9377d711217532a44973edcf63c2af0b7f7969d8. never merged.completed first-run gates on
b4a45605(before test/format-only follow-ups):CARGO_BUILD_JOBS=4under Nix and~/bin/slotcargo fmt --checkac4f6f29and4969da0f; final-head check queuedcargo clippy --workspace --all-targets -- -D warningsstd::fs::renamein highwater.rs and token.rscargo test --workspacecargo test -j4 -p calternal-store tests::restore::failures -- --nocapturebunx vitest run src/lib/action-outbox.test.tsCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/editor.spec.ts e2e/list-catch-up.spec.ts e2e/lazy-offline.spec.ts e2e/served.spec.ts --project=chromium-1440 --workers=1 --output="$HOME/p0b-output-chromium" --reporter=line--project=webkit-1440 --workers=1 --output="$HOME/p0b-output-webkit" --reporter=linewebkit trace: the timeout is in the page fixture (
BrowserContext.newPage), before the test body or navigation. no flake exemption yet: candidate and dev--repeat-each=10checks are queued. no timeout or assertion was relaxed.failure-table audit: the shared rollback row is covered by backup-after-start, backup-before-rotation, whole-host snapshot, lost WAL tail/reused seq, missing entry, missing/torn/changed/ahead witness, first deploy and failed first deploy, failed durable witness rewrite, concurrent proofs, timer, and durable rotation tests. the stale-action row is covered by independent fields, equal values, own unconfirmed op ids, accepted pending actions, two tabs/late retry, rebuild, epoch replay, and unchanged notes patch merging. added a two-session real-websocket snapshot-rollback test for the previously untested requirement that an active session must end with
sync_token_expired; its disabled-notification red check and production check are queued. mail-specific provider/sender failure rows belong to later mail implementation, not these two shared helpers.final-head runs are queued behind the live train locks: fmt, clippy, workspace
--no-fail-fast, expanded save/sync browser cases (large pastes, deploy reload, offline prefs), and the new live regression. release candidate/dev servers and disposable probes have built successfully and their relayed SHA-256 hashes match. perf is unmeasured: another qualification holds the pair; p0b waits on perf-01 before acquiring perf-02.blocking functional findings remain the three regressions recorded above. no performance pass and no shipping approval are claimed.
first-run raw evidence, mutation logs, webkit trace, probe sources and binary hashes. latest test-only follow-up
dd16a8c9accepts a close frame or transport closure after the required structured expiry error, while rejecting any later application message. its red/green run remains queued; no new pass is claimed.qualification complete: does not qualify to ship. functional regressions, clippy errors, a reproduced webkit failure, and perf regressions remain. no gate or budget was loosened. PR remains open and unmerged; no approved code review is recorded.
head
dd16a8c93a644f49df1b633bef91f491c2ff20cb; dev baseline9377d711217532a44973edcf63c2af0b7f7969d8. origin/dev was merged and pushed infa1253fb. a final fetch confirms the branch is not behind dev. all follow-ups after perf buildb4a45605bb92f283844b16e14fb2511ffceba8f6change tests or formatting only; measured application code is identical to the final head. binaries were built on dev-01 and SHA-256 verified on dev-01, control and perf-01. raw logs, all samples, scripts, lock evidence, traces and cleanup records.all heavy gates ran in Nix through ordinary
~/bin/slot, withCARGO_BUILD_JOBS=4. Rust commands ran in~/Developer/Worktrees/p0bon dev-01; browser commands ran fromapps/webon the specified host. the signed-server wrapper adds a unique--secretspath outside each fixture's data directory.cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningsstd::fs::renamein highwater.rs:54 and token.rs:44 is disallowedb4a45605cargo test --workspacecargo test --workspace --no-fail-fastb4a45605cargo test -j4 -p calternal-store tests::restore::failures -- --nocapturecargo test -j4 -p calternal-server --test restore -- --nocaptureb4a45605bunx vitest run src/lib/action-outbox.test.tsCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/editor.spec.ts e2e/list-catch-up.spec.ts e2e/lazy-offline.spec.ts e2e/served.spec.ts e2e/big-paste.spec.ts e2e/pwa-update.spec.ts --project=chromium-1440 --workers=1 --output="$HOME/p0b-head-output-chromium" --reporter=lineCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/editor.spec.ts e2e/list-catch-up.spec.ts e2e/lazy-offline.spec.ts e2e/served.spec.ts e2e/big-paste.spec.ts e2e/pwa-update.spec.ts --project=webkit-1440 --workers=1 --output="$HOME/p0b-head-output-webkit" --reporter=lineCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/prefs-offline.spec.ts --project=prefs-chromium --workers=1 --output="$HOME/p0b-head-output-prefs" --reporter=lineCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/prefs-offline.spec.ts --project=prefs-webkit --no-deps --workers=1 --output="$HOME/p0b-head-output-prefs" --reporter=lineCALTERNAL_BIN="$HOME/p0b-signed-server" bunx playwright test e2e/editor.spec.ts --grep 'hides markdown syntax' --project=webkit-1440 --workers=1 --repeat-each=10 --output="$HOME/p0b-repeat-head" --reporter=lineCALTERNAL_BIN="$HOME/Developer/Worktrees/p0b-webkit-dev/target/debug/calternal" bunx playwright test e2e/editor.spec.ts --grep 'hides markdown syntax' --project=webkit-1440 --workers=1 --repeat-each=10 --output="$HOME/p0b-repeat-dev" --reporter=lineDEBUG=pw:webserver CALTERNAL_BIN="$HOME/p0b-baseline-server" bunx playwright test e2e/editor.spec.ts --grep 'hides markdown syntax' --project=webkit-1440 --workers=1 --repeat-each=10 --output="$HOME/p0b-repeat-dev2" --reporter=linewebkit's first four-spec run was 20 pass / 3 skip / 1 timeout. both its original trace and the candidate repeat-6 trace stall at
BrowserContext.newPage, before navigation or test assertions. the candidate failure reproduced, so the brief's non-reproducing-flake exemption does not apply. neither the 30 s test deadline nor the 60 s server deadline was increased. the expanded webkit skips are the existing Layout Instability API case, CDP-only IME, Chromium-only offline SW inspection, and the existing signed-out SW routing fixme.blocking code findings:
restore.rs:108–116publishes the new epoch and restored counter even when durable witness rewrite fails.a_failed_highwater_rewrite_does_not_publish_the_new_epochfails.restore.rs:61persists the first-deploy flag before writing the witness. an unavailable witness path followed by a repaired retry rotates an existing user.a_failed_first_deploy_can_retry_without_rotatingfails.engine/execute.rs:70returns an unsigned head for read-only action replies.read_only_action_replies_carry_signed_tokensfails with 2 parts instead of 4.SyncTokenimport in execute.rs, unusedwire, and overwrittenheadin storage-bench.rs. clippy currently stops earlier at the two disallowed renames.failure-table coverage: the shared rollback row has tests for backup after start, backup before an earlier rotation, whole-host snapshot, lost WAL tail/reused seq, missing entry, missing/torn/changed/ahead witness, first deploy and failed first-deploy retry, failed witness rewrite, concurrent proofs, timer, durable rotation, signed snapshot replies and expiry of both active live sessions. the shared stale-action row covers independent fields, equal values, own unconfirmed op ids, pending actions, two tabs/late retry, rebuild, older-epoch replay and unchanged notes merging.
stale_patch_keeps_newer_typingpasses in the complete workspace run. three added already-implemented store branches were mutation checked: removing each check makes its test fail at its assertion; final production coverage passes. the live-session test was run against disabled notifications first, then restored production. the three uncovered code defects fail on their first execution. mail-specific provider/sender rows remain later mail milestones, not tests of these shared helpers.perf ran on perf-01 while both
~/bin/perf-locklocks were held, acquired perf-01 then perf-02. each mode runs dev/dev ABAB ×5, then dev/head ABAB ×5: 10 windows per arm, 120 completed invocations. both holders were stopped immediately after matrix completion. the full matrix began at 14:03:39 ist. ordinary commits remain NORMAL; no budget was changed.an initial attempt completed journal/open windows but failed at room server startup: the existing perf helper created
state/server.logbeforeserve --create, so storage pairing correctly refused a nonempty state root. its 80 partial rows and diagnostic log are retained. the full matrix was restarted from the beginning with an identical wrapper for both binaries that moves the preopened log outside state; this preserves the storage gate. process-to-healthz timings include that common wrapper. the following tables use only the complete 120-invocation matrix, with every window retained.sampling per window: 20k journal preload, 4k timed commits, 2k checked positions at head and mid-log, 300 pages of 100; store open with 1m journal entries; 200 real room edits/acks and peer convergence, 30
notes.savecalls, 50 reconnects and process-to-healthz. non-startup cells are medians of 10 per-window quantiles. startup cells are nearest-rank quantiles over 10 observations, so their p95 and p99 coincide. raw samples retain all counts. existing-store open includes the external witness read/rewrite; process healthz uses a fresh lazy-open server.positive deltas are slower. no positive result is waived as under budget. the save/ack tail is noisy relative to its control; that does not establish flatness. the other large store/open regressions and the notes.save tail prevent a perf pass.
all absolute values and deltas (milliseconds)
open risks and gaps:
POSIX_FADV_DONTNEED; it includes high-water I/O and is labeled accordingly. it is not a dropped-machine-page-cache claim. startup tail estimates have only 10 observations per arm.--secretsoutside backups. the PR's declaredattachments.addretry-after-lost-idempotency-record follow-up remains; no production deployment or settings were changed.all owned measurement/gate jobs are finished. both perf holders are inactive. owned targets, large build outputs, perf binaries and seeded fixtures were removed; recorded browser fixture paths were cleaned or confirmed gone. cleanup records and final free-space values are attached. no PR merge was performed.
Orchestrator: #928 is RED, so it doesn't ship as-is.
Blockers:
Correctness. Three tests the new work added now fail:
Clippy. Two disallowed filesystem renames. Use the fsio helper.
Perf. Two paths are orders of magnitude slower than dev:
That can only come from scanning the log. The position check and the startup check must be O(1), or a single bounded read of the high-water file, never a walk of the log. Catch-up page (+31 %) and reconnect (+5–14 %) also move.
WebKit. The timeout reproduces at 1/10 on the candidate and 0/10 on dev.
Next: one fix round on Codex, targeting O(1) checks and the three correctness bugs. If that round still finds new problems, the rethink rule applies: write a simpler design here first.
kayg referenced this pull request2026-11-05 23:21:11 +00:00
Status update:
Qualification at head
2127c26fbdGates
Rust gates ran on dev-01 in the PR worktree via
~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash /home/claude/p928-rust-gates-inner-2127.shwithCARGO_BUILD_JOBS=4.cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace --no-fail-fastcd apps/web && CALTERNAL_BIN=/home/claude/p928-2127-save-sync-signed bunx playwright test e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1 --output=/home/claude/p928-2127-save-sync-chromium --reporter=linee2e/editor.spec.ts e2e/list-catch-up.spec.ts, projectswebkit-1440andwebkit-390hides markdown syntax, candidate and dev,--repeat-each=20Post-pin perf session
Fresh CPU-pinned ABAB x5 session, with both shared perf locks acquired in order (perf-01 then perf-02) and held for the whole session. It completed 120 measurements including a dev-vs-dev control; both locks were released afterward. The corrected room setup used candidate secrets and 200 save samples per room run. The earlier exploratory room run with invalid setup was excluded.
Positive percentages mean slower. Non-startup cells are medians of 10 per-window quantiles. Startup cells use 10 observations; p50 is the median and p95/p99 are nearest-rank (the maximum here). Values are candidate vs dev and dev-vs-dev control, respectively.
catch_up_pagecommitnotes_saveposition_headposition_midreconnect_readysave_ackstartup_1m_file_evictedstartup_healthzsync_round_tripPerf is not a pass under the flat-or-better gate:
catch_up_pageis positive at all three quantiles andnotes_savep95 is +2.82%. The complete table also shows positive tails forsave_ack,startup_healthz, andsync_round_trip; they are not rounded away or waived. No merge performed.Current-head gates at
95c42ddbe1af2e3228f812a4a0e449cbd1b7d6f3cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace --no-fail-fastCALTERNAL_BIN="$HOME/p928-95c42-signed-server" P928_SECRETS_ROOT="$HOME/p928-95c42-web-secrets.o1gNyn" bunx playwright test e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1 --output="$HOME/p928-95c42-save-sync-chromium" --reporter=linee2e/editor.spec.ts e2e/list-catch-up.spec.ts, projectswebkit-1440andwebkit-390hides markdown syntax, candidate and dev, repeat 20e2e/editor.spec.ts --grep 'hides markdown syntax' --project=webkit-1440 --workers=1 --repeat-each=20Rust gates ran on dev-01 in the PR worktree through
~/bin/slotand Nix withCARGO_BUILD_JOBS=4. The first setup attempt hit the full/tmpinode table before cargo started; the successful retry used a privateTMPDIRon/.The earlier perf matrix at
2127c26predates this head and stays separate. No pre-pin samples are included in the new post-pin session.Post-pin perf session at
95c42ddbe1af2e3228f812a4a0e449cbd1b7d6f3Candidate:
95c42ddbe1af2e3228f812a4a0e449cbd1b7d6f3; dev baseline:7a9bd54eb8f385364c712198af45b31d706493fd.The run started after the perf VMs returned from the CPU pin reboot. The shared lock was acquired on perf-01, then perf-02, and both were held through the ABAB ×5 and the same-session dev-vs-dev control. Both were released after completion. The result file contains 60 ABAB rows and 60 control rows; each arm has 10 window observations per metric. Positive deltas mean slower. Non-startup cells are medians of 10 per-window p50/p95/p99 values. Startup uses 10 observations with median p50 and nearest-rank p95/p99.
catch_up_pagecommitposition_headposition_midstartup_1m_file_evictednotes_savereconnect_readysave_ackstartup_healthzsync_round_tripThis does not qualify yet:
catch_up_pageis slower at all three quantiles,reconnect_readyp99 is slower, and startup p50 andnotes_savep95 are slightly slower.notes_savep99 is 4.31% faster. The control itself shows catch-up tail drift, so I am tracing the hot-page path before the next session. This result is separate from the earlier2127c26matrix.Exact measurement command on perf-01:
systemd-run --user --unit=p928-95c42-postpin-run-1 --collect -p RuntimeMaxSec=7200 /bin/bash /home/claude/p928-perf-95c42-postpin-20261106-run1/run-all.shLock commands, run in order:
systemd-run --user --unit=p928-95c42-postpin-lock-01 --collect -p RuntimeMaxSec=3600 /home/claude/bin/perf-lock sleep 3600systemd-run --user --unit=p928-95c42-postpin-lock-02 --collect -p RuntimeMaxSec=3600 /home/claude/bin/perf-lock sleep 3600Qualification status
hides markdown syntaxinvestigation: pending: std VMs paused. No jobs will start on dev-02 or the Mac while paused.P1b is now open at #995 with its design at the top. It will store the cursor with search documents, validate it through
Engine::position, and page catch-up throughEngine::journal. P1b does not add a token format or read/copy the high-water value; your P0b implementation remains the sole owner of signed proofs and restore detection. An expired cursor triggers a paged full reconcile. The implementation will rebase onto #928 if it merges first and use the merged engine contract.Qualification at head
5f7b057f2527d9c09f7aab1ff50bf4d7f07bc0f5Rust and save/sync browser gates pass. Performance qualification does not pass. Positive percentages mean slower; non-startup rows are medians of 10 per-window p50/p95/p99 values. Startup uses 10 observations (median p50, nearest-rank p95/p99).
Rust and browser gates
Rust ran on dev-01 in the candidate worktree with
CARGO_BUILD_JOBS=4, through~/bin/slotand Nix.CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo fmt --check'CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo clippy --workspace --all-targets -- -D warnings'CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo test --workspace --no-fail-fast'Save/sync browser specs were
e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts, one worker:~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/p928-final-3c824/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/p928-final-3c824/target/debug/calternal bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --workers=1'~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/p928-final-3c824/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/p928-final-3c824/target/debug/calternal bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=chromium-390 --workers=1'~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/p928-webkit-candidate/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-webkit-candidate/target/debug/calternal bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --workers=1'~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/p928-webkit-candidate/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-webkit-candidate/target/debug/calternal bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=webkit-390 --workers=1'The WebKit
hides markdown syntaxcase was repeated on quiet dev-02 with--repeat-each=20. Candidate command:DEBUG=pw:webserver CALTERNAL_BIN=/home/cua/p928-codex-5f7-webkit-candidate-signed bunx playwright test e2e/editor.spec.ts --grep "hides markdown syntax" --project=webkit-1440 --workers=1 --repeat-each=20 --output=/home/cua/p928-quiet-repeat-candidate-5f7 --reporter=line. Dev used the same command withCALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-dev-current-20261106/target/debug/calternaland output/home/cua/p928-quiet-repeat-dev-935fd-5f7. Both passed 20/20; the initial candidate-only failure did not reproduce. Tracking issue: #996.Full post-pin perf matrix
Candidate
5f7b057fvs dev935fdcacd9cf2837d313817d6786d8a810b3bdaa; perf epoch2026-11-06 14:16 IST. Both shared locks were held throughout, perf-01 then perf-02, and the same-session dev-vs-dev control ran before the candidate matrix. The 120 cells finished at 2026-11-07 00:16 UTC. Measurement wrapper:/bin/bash /home/claude/p928-perf-5f7-20261106-run1/run-matrix.sh, which runs/home/claude/bin/slot /home/claude/.nix-profile/bin/python3 run.py.catch_up_pagecommitposition_headposition_midstartup_1m_file_evictednotes_savereconnect_readysave_ackstartup_healthzsync_round_tripThis matrix fails the flat-or-better gate:
catch_up_pagep50,reconnect_readyp50, andnotes_savep95/p99 are positive; other positive deltas are shown above.Eight-second-idle diagnostic
On the same perf epoch and under both shared locks, a 40-cell ABAB ×5 diagnostic inserted eight seconds before the 30 measured
notes_savecalls. It started at 2026-11-07 03:41:36 UTC and completed at 03:50:53 UTC. Exact launch:systemd-run --user --unit=p928-5f7-idle-diagnostic --collect -p RuntimeMaxSec=7200 /bin/bash /home/claude/p928-perf-5f7-delay-20261106/run-matrix.sh. All four probe/server hashes matched the dev-01 relay copies.notes_savereconnect_readysave_ackstartup_healthzsync_round_tripThe idle shifts the
notes_savep99 from +17.02% to -12.32% while the control stays near flat (-0.62% to -0.58%). This is timing-dependent evidence that the five-second durable witness flush overlaps the save batch. The full matrix remains red; the diagnostic idle is not a production fix. This new design finding is recorded at the top of the PR. No merge performed.Qualification at
7b36b0d463f11e0138b15a6b629b4589c309ae38Gates
CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --no-fail-fast'~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/p928-perf-fix-5fe8/apps/web && CALTERNAL_BIN=/home/claude/p928-ca27-signed-server bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1 --output=/home/claude/p928-7b36-save-sync-chromium --reporter=line'~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/p928-path-identity-20261107/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-path-identity-20261107/target/debug/calternal bunx playwright test e2e/editor.spec.ts e2e/big-paste.spec.ts e2e/traps.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --project=webkit-390 --workers=1 --output=/home/cua/p928-7b36-save-sync-webkit --reporter=line'hides markdown syntax, candidate 20 repeats~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/p928-path-identity-20261107/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-path-identity-20261107/target/debug/calternal bunx playwright test e2e/editor.spec.ts --grep "hides markdown syntax" --project=webkit-1440 --workers=1 --repeat-each=20 --output=/home/cua/p928-7b36-repeat-candidate --reporter=line'hides markdown syntax, dev 20 repeats~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/p928-dev-current-20261106/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/p928-dev-current-20261106/target/debug/calternal bunx playwright test e2e/editor.spec.ts --grep "hides markdown syntax" --project=webkit-1440 --workers=1 --repeat-each=20 --output=/home/cua/p928-792b-repeat-dev --reporter=line'Post-pin no-idle ABAB x5, with same-session dev-vs-dev control
Candidate
7b36b0d; dev792b33536. Both perf locks were held on perf-01 and perf-02 for the full run. The post-pin epoch and boot IDs were unchanged. The run completed 120 cells with exit 0. Probe hashes were matched to the release build on both arms; server binaries were both debug builds. The room mode had no idle insert. Startup uses fsync plus per-filePOSIX_FADV_DONTNEED, not a whole-machine cache drop. Positive percentages mean slower. Non-startup values are medians of 10 per-window p50/p95/p99 values; startup uses 10 observations, nearest-rank p95/p99.catch_up_pagecommitposition_headposition_midstartup_1m_file_evictednotes_savereconnect_readysave_ackstartup_healthzsync_round_tripThis fails the unchanged all-path gate.
commitis slower at all three quantiles beyond the control;reconnect_readyp50/p99,startup_healthzp95/p99, andsync_round_tripp50 are also outside the control.save_ackp50 is +0.53% vs dev and +0.20% in the control. Thestartup_healthzp95/p99 is one 143.93 ms sample among 10 candidate observations (the other nine candidate observations were at or below 73.57 ms), so it is shown without treating it as a broad shift.notes_saveis flat or better in this matrix.Continuous-save soak, 200 ms cadence, 180 seconds per arm
Each arm completed 900 saves with zero retries. Candidate values sit within the two dev controls' variation; candidate max is lower than both controls.
notes_savep95The p99 candidate value is 0.46 ms above dev A, within the 2.78 ms dev-vs-dev p99 spread. Save starts remained at a 200 ms median interval.
Current result
The idle-gated bounded flush fixed the measured
notes_savetail, but the fresh matrix has new performance regressions outside the control, especiallycommit. The design is not qualified under the all-path gate and needs a performance rethink before this PR proceeds. No merge.P1b #995 now adds a Notes source-generation fence. It reads the accepted version from each
notes.savedentry, waits on the existing Notes publication watch when the pinned source is behind, and rechecks the saved cursor tail on restart. The search side continues to use the enginepositionandjournalcontract. It does not inspect token fields, read the high-water file, or duplicate #918 restore detection; P1b does not need the high-water value. If #928 merges first, #995 will rebase and use its interface.Startup rerun and commit bisect
Startup metrics: 20 observations per arm
Candidate
7b36b0d463f11e0138b15a6b629b4589c309ae38; dev792b33536bc9743904bf7a5602386027183d0776. The run had 160 samples, ABAB ×10 per phase for each metric, with a same-session dev-vs-dev phase followed by dev-vs-candidate. The perf-01 and perf-02 lock holders bracketed the run; both boot IDs match the post-pin epoch. Results below are milliseconds. Each arm shows median, IQR, and min–max. Paired deltas compare adjacent A/B observations within cycles.startup_healthzstartup_healthzstartup_1m_file_evictedstartup_1m_file_evictedstartup_healthzis flat within the paired control spread despite a phase-to-phase shift in absolute times.startup_1m_file_evictedhas a small positive median shift of 0.073 ms; its paired spread is wide and overlaps the control, with 12/20 candidate pairs slower. The p95/p99 tail outliers are dev-side cache-eviction samples (up to 19.862 ms); the candidate arm's p95/p99 are 0.931/0.979 ms. This does not show a broad startup regression, but the small p50 shift remains visible and will be checked again in the final matrix.Commit micro-bisect
The 60-row micro-bisect used three ABAB cycles per stage, six 4,000-commit observations per arm. Positive deltas mean slower.
7b36b0dThe final-head p99 is inside the broad same-session dev control IQR; the full matrix's commit regression remains a gate failure. Source inspection identifies the per-call work to remove:
UserStore::commit/commit_onceactivity guards, the writer's dirty-age clock/CAS, andcommit_inner's seven-atomic witness snapshot publication.fstat/statis confined to the flush worker and does not run on commit. The selected simpler design is now at the top of the PR description; no code has been patched yet.Startup rerun at
95af4df2cc0add210d70850c2c092a23cebe4e5dCandidate was
95af4df2cc0add210d70850c2c092a23cebe4e5d; dev was792b33536bc9743904bf7a5602386027183d0776. The post-pin epoch and both perf VM boot IDs match the session metadata. Both shared perf locks were held for the full run. Startup and room scenarios ran ABAB ×10 per phase, giving 20 observations per arm. Values are milliseconds: median of the per-invocation p50, Tukey IQR, and min–max.startup_healthzstartup_healthzstartup_healthzstartup_healthzstartup_1m_file_evictedstartup_1m_file_evictedstartup_1m_file_evictedstartup_1m_file_evictedPaired B/A median was −0.30% for
startup_healthz(9/20 candidate pairs slower; control −3.48%, 7/20 slower), so there is no paired shift beyond the phase noise. Forstartup_1m_file_evicted, candidate paired median was +35.75% (16/20 slower) versus control −2.59% (10/20 slower). The absolute paired median difference was 0.188 ms. The wide tails remain noisy, but the 1M-entry median shift is visible in the paired observations and is not explained by the p95/p99 sample count alone.The current startup path creates and syncs a blank page before the initial valid witness slot is ready, then syncs the slot separately. That is the leading cause to test by deferring first-page allocation until the valid mark is written.
Runner:
/home/claude/p928-final-95af-perf-20261108-run1/run-all.sh(startup data insamples.jsonl; run metadata inmetadata.json). No idle insert was used.Startup diagnostic at
8357e925a(merged with dev2fa9e18fb)Takeover of #928 after the previous agent stopped.
8357e925amerges current dev; the one conflict (crates/calternal/tests/notes_crash_typing.rs) takes dev's fix for the same flake, so the branch's own test-only fix drops out of the diff.Rust gates (dev-01, merged head)
CARGO_BUILD_JOBS=4 ~/bin/slot nix develop -c bash -c "cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --no-fail-fast"inp928-takeover-cand: PASS (exit 0).startup_1m_file_evicted, 20 observations per armSame session, both perf locks held (perf-01, then perf-02), 2026-11-09 08:12–08:18 UTC, post-pin epoch. Release probes, with sha256 checked on dev-01 and on perf-01. Each cycle runs a dev-vs-dev pair, a dev/candidate ABAB, then another dev-vs-dev pair. Positive means slower.
Cause: the +35.75% at
95af4df2came fromrestored_at_opencallingwrite_nowon a valid older same-epoch mark: a slot rewrite plusfdatasyncduring open.f71379a1cleaves that mark in place and has the flush worker advance it. With that change the fully evicted case is flat against its control. Path-identity checks are unchanged and stay on the flush worker.The "secrets warm" row is a diagnostic. Its paired IQR (−10%…+67%) overlaps the control's (−91%…+27%). The parallel-read variant shows no gain, so it is not committed. The full ABAB matrix and soak come next, in a FIFO perf turn.
Final ABAB matrix and soak at
8357e925avs dev2fa9e18fbFIFO perf turn (
~/bin/perf-turn p928), perf-01 then perf-02 locks held 12:06–12:29 UTC 2026-11-09, post-pin epoch. Release probes and release servers (server-cand746b4fdd…,server-dev883e18bd…), sha256 checked on perf-01 at start. Each cycle: dev-vs-dev control pair, dev/candidate ABAB, control pair. Journal ×3 cycles (6 obs/arm, kept small for the 90-min turn), startup ×10 and room ×10 (20 obs/arm). No idle insert. Positive = slower. Non-startup values are medians of per-run p50/p95/p99; startup uses per-run times with nearest-rank p95/p99.commitposition_headposition_midcatch_up_pagestartup_1m_file_evictedstartup_healthznotes_savesave_acksync_round_tripreconnect_readystartup_1m_file_evictedmatches its control. Both phases show the same first-of-pair order effect (paired −50.0% vs control −52.3%; candidate arm median 0.746 ms vs control B 0.622 ms, dev and control A medians inflated by eviction outliers). The +35.75% from #86388 is gone.Soak: 200 ms cadence, 180 s per arm, 900 saves, 0 retries each
notes_savep50Open: room-scenario tails
save_ackp95/p99 (+4.00%/+5.51% vs control −3.22%/−8.94%),startup_healthzpaired p50 (+2.91% vs −0.59%),notes_savep99, and soak p95/max sit above dev. Their paired IQRs overlap the controls (for examplestartup_healthz[−1.6%, +5.6%] vs [−4.1%, +6.1%],notes_savep95 [−1.2%, +9.5%] vs [−1.9%, +7.8%]), but 20 observations cannot show they are flat. A room-only rerun with 40 obs/arm plus a second soak is queued in the FIFO line.Room rerun (40 obs/arm) and second soak at
8357e925avs dev2fa9e18fbFIFO turn
p928, perf-01 then perf-02 locks held 12:56:39–13:24:33 UTC 2026-11-09, same binaries and hashes as comment 88778. Room ×20 ABAB cycles with control pairs around each cycle. Positive = slower.notes_savesave_acksync_round_tripreconnect_readystartup_healthzSoak 2 (200 ms cadence, 180 s per arm, 900 saves, 0 retries each),
notes_savep50 / p95 / p99 / max: dev A 21.040 / 27.421 / 37.557 / 58.461 ms; candidate 20.928 / 27.121 / 38.256 / 171.097 ms; dev B 21.015 / 28.036 / 37.250 / 65.754 ms.Verdict across both sessions
startup_1m_file_evictedis flat against its control (cause:restored_at_openusedwrite_nowon a valid older mark;f71379a1cdefers that to the flush worker; the path-identity checks stay on the flush worker).startup_healthz+2.15% (control +0.56%), about +1.9 ms. Every room run is a fresh--createinstall with a fresh--secretsdir, so this times first-ever startup: durable key creation plus the first durable witness write. The design requires both before signed tokens go out. It is not a per-restart cost, and the store-level reopen (startup_1m_file_evicted) is flat.sync_round_trip+2.37% (control +0.36%, ≈ +5 µs) andsave_ack+0.93% (control −0.59%, ≈ +0.07 ms): per-entry signed tokens on the live path. Dev does not sign at all.fdatasynclanding next to a save during continuous typing.These are new work required by #918/#919, not accidental cost, but under the no-regression rule they block. Needs an owner decision: accept them as the price of signed tokens and restore detection, or ask for further work (for example moving first-use key and witness creation off the
/healthzpath, or aligning the max-age flush away from in-flight saves).Final matrix at
ee2bc32dcvs dev2fa9e18fbOne FIFO turn (
perf-turn p928), perf-01 then perf-02 locks held 00:18–00:51 UTC 2026-11-10. Release builds, sha256 checked on dev-01 and perf-01 (probe-cand38a3fdf4…,server-canddf8b1a1e…,probe-deve0ce08c9…,server-dev883e18bd…). ABAB with dev-vs-dev control pairs around every cycle: journal ×5 (10 observations/arm), startup ×10 (20/arm), room ×20 and boot ×20 (40/arm). Positive = slower.Changes since comment 88839
b825864283d1eaa7baa949fa358EventRecorder::save_scope: room typing saves mark a save in flight (never opens a store)1f55329ffsince()ee2bc32dc/healthznever waited for first-install durability: strace shows no secrets-folder access before or within 2 s afterlisten. Key and witness are made durable on the first store open, before any signed token is returned; that cost is now visible asfirst_token_fresh.Matrix
commitposition_headposition_midcatch_up_pagestartup_1m_file_evictedstartup_healthzstartup_healthz_existingfirst_token_existingfirst_token_freshnotes_savesave_acksync_round_tripreconnect_readyBoot (startup only, 40 obs/arm; candidate server with and without
--secrets)first_token_existingfirst_token_freshstartup_healthzstartup_healthz_existingfirst_token_existingfirst_token_freshstartup_healthzstartup_healthz_existingSoak (200 ms cadence, 180 s per arm, 900 saves, 0 retries each)
Gates at
ee2bc32dccargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --no-fail-fast: PASS, 1,691 passed, 22 ignored.editor,big-paste,traps,list-catch-up): PASS, 44 passed, 8 skipped.Open
first_token_fresh+50% (about +20 ms) is the required key and witness durability, moved from startup to the first token./healthz: the room phase is flat (−1.29% vs −0.13%); the boot phase reads +1.30% vs −1.21% with--secretsand flat without. No syscall difference was found, so this looks like noise, but it is reported as measured.Soak max: cause and plan
Diagnosis only; no code changed for this. Head
ee2bc32dc, dev2fa9e18fb.How it was measured
One FIFO turn (
perf-turn p928), perf-01 then perf-02 locks held 04:43–05:01 UTC 2026-11-10. Four 180 s soak arms, interleaved dev A, candidate 1, dev B, candidate 2, at a 200 ms cadence (900 saves per arm, 0 retries). Same release servers as comment 89376 (server-canddf8b1a1e…,server-dev883e18bd…).notes.savestart and end (Unix µs), and the size of every*-walfile under the state folder.strace --seccomp-bpf -f -ttt -T -y -e trace=fsync,fdatasync,openat. Only those calls stop the process, and-ynames the file behind each fd. The overhead is the same for both builds (p50 about 22 ms in every arm, vs 20.8 ms untraced).secrets/users/<u>/headand its first-install files) with the saves around it.Finding
#928 adds no sync to any save. Each candidate arm has 10 witness syncs: 4 at first install (key tmp file, secrets folder,
head, folder again) and 6 maximum-age flushes about 30 s apart. Each takes 3.3–7.1 ms. None overlaps a save window. The next save starts 75.5–267.5 ms after a witness sync ends. Every other sync class has the same count in both builds (for example 2,731 history WAL and 904 note tmpfile syncs per arm), and the journal WAL synced 3 times per arm in both.What the slowest save waits on, in every arm, is dev's own save chain on a shared-disk burst. Every save runs four syncs in series:
fdatasync;fsync;Notes/folderfsync;fdatasync.Normally that's about 3.4 + 9.6 + 6.6 + 3.4 ms. The worst saves are the same chain with each sync stretched by perf-01/perf-02's shared disk. Dev B save 100 took 86.8 ms: tmpfile 41.9 ms, folder 21.0 ms, history WAL 21.1 + 21.1 ms. Dev A save 765 took 89.4 ms: 33.1 + 20.9 + 33.0 ms. Candidate 2's worst (save 329, 66.7 ms) is the same chain at 31.4 + 21.6 + 10.7 ms.
There is also one deterministic slow save per run, on dev and candidate alike. Save 93 in all four arms runs a
meta.dbWAL checkpoint inside the save.meta.db-walis at 4,128,272 bytes (SQLite's 1000-page auto-checkpoint), so the save adds ameta.db-walfdatasync(14.7–17.8 ms) and ameta.dbfdatasync(6.6–10.0 ms), giving 48–59 ms. This is outside #928 and present on dev.The earlier candidate-only max (88.4 ms vs dev 45.7/52.0 ms in comment 89376) was this same shared-disk burst landing on the candidate arm in that session. With one 180 s arm per side, a single burst decides which arm "has the max". In this session two bursts landed on the dev arms.
Proposed change (one, not implemented)
Change the soak gate, not #928's code: judge
notes_savemax per arm against the dev arms' range over at least three interleaved arms per side (ABABAB), and keep p50/p95/p99 as the main gate. When a candidate arm exceeds the dev range, run this strace join and accept only if the candidate adds a sync inside a save window.save_ack, which stay gated. Cost: three soak arms per side add about 9 minutes per turn.A separate, dev-wide follow-up (its own issue, not #928): the deterministic in-save
meta.dbcheckpoint (finding 3) could move to a background passive checkpoint below the 4 MB threshold, taking about 20–25 ms off one save in every ~90 at this cadence.first_token_fresh +50% (+20 ms): accept or decline
token.keyatomic write with fsyncs plus the first witness slot write,fdatasyncand folder sync, the four first-install witness-class syncs above at about 6.5 ms each.startup_healthz_existing+0.13% (control +1.06%),first_token_existing−0.89% (control −2.44%) in the boot phase./healthzdoes not wait for it (strace: no secrets-folder access before or within 2 s afterlisten).READY pending kayg's
first_token_freshdecisionHead
ee2bc32dc. The remaining perf item is closed:ee2bc32dc(comment 89376): Rust fmt, clippy and workspace tests pass (1,691 passed); Chromium 1440/390 save/sync 44 passed; WebKit 1440/390 36 passed. Everything else in the matrix is flat or better than dev.One decision is open, for kayg:
first_token_fresh+50% (about +20 ms), once per user on the first store open after a fresh install, to make the token key and witness durable before the first signed token (#918). The details are in comment 89399. Not merged; waiting on that answer.The in-save SQLite checkpoints found during the diagnosis are dev-wide and tracked separately in #1051.
READY: head
e5559455f, rebased onto devfe2efbf11kayg accepted
first_token_fresh(+20 ms once per user on a fresh install, 11-10). The soak gate is decided (#1050). Nothing open.Rebase: 104 commits replayed onto
fe2efbf11.crates/server/src/live.rsconflicted with dev's credential revalidation during catch-up (#1048 line). Resolution:catch_upkeeps dev'sauthargument and signed-out early return, and also returns the signed page token, asResult<Option<(i64, String)>>. The ready message sends that token; the lag path keeps the position only. One branch commit (5e68c649e, a crash-typing test fix) was dropped because dev's own fix supersedes it; that file now equals dev's. The PR diff is 39 files, the same as before the rebase. The branch merges cleanly into dev.Gates at
e5559455f:cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --no-fail-fasteditor,big-paste,traps,list-catch-up),chromium-1440+chromium-390admin-sharing.spec.ts,admin-chromium-1440+admin-chromium-390,--repeat-each=2webkit-1440+webkit-390admin-sharing.spec.ts,admin-webkit-1440+admin-webkit-390,--repeat-each=2Perf: no code changed in the rebase apart from the
live.rsmerge, which only combines the two return values. The last matrix (comment 89376) and the soak diagnosis (comment 89399) stand.Merged into dev (
4274dafb4, integration merge with #1043/#928/#917/#934; clippy -D warnings + workspace tests green on dev-01). Closing.Pull request closed