Add CardDAV cache and read path (ADR 0064 pieces 3 and 4), rebased #776
Open
kayg
wants to merge 1 commit from
issue/carddav-read-r2 into dev
pull from: issue/carddav-read-r2
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:feat/contrast
kayg:fix/shared-row-swipes
kayg:feat/pass8
kayg:t3/paged-search-reconcile-final
kayg:storage/wal-checkpoint-off-save-1051
kayg:review/calendar-2
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:shared/restore-and-stale-918-919
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: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!776
Loading…
Reference in a new issue
No description provided.
Delete branch "issue/carddav-read-r2"
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?
Round 2 shared epoch fix implemented, FINAL review pending
Head
113af4f8. Status table, exact gates and limits. Held-reader tokens are validated against committed epochs; mid-read replacement fails closed. Updated #777 is re-merged into #794 at21e678b2, pushed after green gates. Never merged into dev.Earlier implementation records below are historical.
Round 1 fixes implemented, review pending
Head
f3d6361d: bounded wire-history retention and RFC 6578 token expiry. Plan and bounds; status table, exact gates and open risks. All scoped VM gates passed. Never merged into dev.The original implementation record below is historical; its unbounded-retention/rebuild gaps are superseded by this round.
Replacement for #734 (same change, rebased onto current dev as one commit; Forgejo marked #734 unmergeable). Originals are untouched. Refs #657, ADR 0064, D0 in contacts-0064-plan.
Contents:
crates/carddav(cache inusers/<uuid>/carddav.db, journal projection, read-only DAV adapter, unmounted),contacts-actionsprojection,contactswire index. See #734 for the full description.Merge order: this PR first, then the perf follow-up.
Gates (cua@calternal-dev-02-std, aarch64, Nix dev shell, via slot, -j4)
cargo fmt --all -- --check: cleancargo clippy -j4 -p calternal-carddav -p calternal-contacts -p calternal-contacts-actions --all-targets -- -D warnings: cleancargo test -j4 -p calternal-carddav -p calternal-contacts -p calternal-contacts-actions: all pass (carddav 7 cache + 2 journal + 10 read + 1 recovery = 20; contacts suites incl. wire_index, hostile, break_*; contacts-actions 130)49ccd9a3. No conflicts and no test breakage from the rebase.Review against ADR 0064 (independent; not the author)
Matches: bridge never writes files (read-only methods, writes get 405); vCard 3.0 precomputed with strong ETags, reads never run the converter; stable user-id URLs and
/.well-known/carddavredirect; paged sync with fixed snapshot, continuation tokens and 507 marker (RFC 6578 3.6); deletes reported as 404; expired/foreign tokens givevalid-sync-token; rebuild is atomic and rotates the epoch; routes unmounted and an authenticator is required; depth infinity refused.Gaps and risks (none block landing; D4 and later should pick them up):
revisionsis append-only and never pruned. Each edit adds a full card row; disk grows with edit count and only a rebuild compacts it. Needs a compaction rule (rebuild also rotates the epoch, so devices resync).changed_idsmaps a fixed list of journal kinds. A new journal kind that touches a contact (for example Trash or tier moves) would be missed until a rebuild. Needs a test that enumerates note-affecting kinds, or a fallback rebuild on unknown kinds. Companion cards (FO1/D2) are not covered yet.<id>.vcfonly; D6 must add the alias table),If-Match/412.rebuild(backup install is atomic by design, covered by the rollback test, not by a real crash).kayg referenced this pull request2026-10-31 16:24:55 +00:00
Independent review, round 1
Reviewed head
49ccd9a338edaf0356f9619579316b4a6446a129, ADRs 0063/0064, the PR bodies and the earlier #734/#739 discussions. Scope: cache/read correctness and data safety; admission, transport caps and auth are assigned to #794.Findings
crates/carddav/src/cache.rs:172inserts a complete wire card and property index on every byte-changing edit. Onlyrebuilddeletes history (:191);source::refreshdoes not schedule a rebuild for an otherwise healthy, same-generation journal. A book with photos therefore retains another full photo for each ordinary text edit, including after the contact is deleted forever. For scale, 1,000 cards with 224 KiB photos and 52 edits add about 11 GiB of photo copies alone. This disposable per-user SSD cache has neither retention nor a byte budget, so normal long-running usage can exhaust shared state storage. Fix: define bounded cache-history retention and token expiry. Either retain the revisions required by a bounded snapshot/delta window and reject older tokens, or atomically compact and rotate the epoch at a size threshold. Do not simply delete revisions while still accepting their tokens. Add an edit-churn test that checks bounded storage, paged snapshots, tombstones and rejection of expired tokens.Checks and limits
changed_ids(crates/contacts-actions/src/projection.rs:17) silently treats unknown kinds as irrelevant. This is a forward-compatibility risk, but I did not establish a currently missing Trash/restore kind: ordinary Trash emitsnotes.deleted, restore emitsnotes.created, and both are covered. Add an exhaustive producer/consumer contract or a conservative rescan for unknown contact/note mutations before extending the event vocabulary.git diff --check 49ccd9a3^ 49ccd9a3passed. Existing reported VM gates were read, not independently rerun.Verdict: fix first. No P0/P1 finding in this PR; bound the disposable revision history before BM4.
round 1 implementation plan (no dev merge):
then push #776 and #777 in order, merge updated #777 into #794, rerun its specified gates and push only if green. #797 remains a downstream consumer; its independent write fixes are outside this task. cheap P2s will be fixed where established; remaining delivery gaps will be listed. review round 1 of maximum 3.
round 1 implementation status at
f3d6361d. pushed, never merged into dev. review remains pending.Retention::max_revisions, default 1,024 wire-changing revisions; durable floor and pruning commit with journal cursorvalid-sync-token; HTTP regression checks 403 then successful empty-token full resyncVM:
cua@calternal-dev-02-std,/home/cua/Developer/Worktrees/carddav-retention-r1, Nix shell through~/bin/slot. logs~/carddav-retention-r1-green.logand~/carddav-retention-r1-space.log, both exit 0. the new churn test failed on the original head (exit 101, old delta still accepted), then passed with the fix.Decision: count-based disposable wire-cache retention, configured by
Cache::open_with_retention; no change to authoritative contact history. one baseline per href plus the recent window supports snapshots. growing the policy cannot resurrect discarded history. freed SQLite pages are reused, so this prevents further edit-driven growth rather than shrinking an already oversized historical file. the existing O(n) rebuild and source inventory are addressed in stacked #777. native Apple replay, perf-VM latency gates, groups/photos delivery and an exhaustive future journal-vocabulary contract remain separate work; unknown kinds currently have no demonstrated missing producer. max 3 independent review rounds; this is the round 1 implementation, not a new review.Independent review, round 2
Reviewed
f3d6361d5c, round-1 finding #issuecomment-71157 and implementation #issuecomment-71194; also traced the inherited code through #777 and the #794 merge80ec1fa3.valid-sync-tokenfollowed by successful empty-token resync. These tests do not cover a reader held across another connection's rebuild: R2-1 below.new finding: R2-1, P2: an already-open cache reader accepts a replaced epoch
Locations: sync.rs:19, cache.rs:247.
epochis cached when a handle opens; rebuild changes the database epoch and only the rebuilding handle's field. Another handle sees new rows/head with its old epoch.Reproduction: seed
a.vcf, open a secondCacheand obtain its token, then rebuild through the writer with onlyb.vcf. The writer correctly rejects the old token. The held reader accepts it without reporting the disappearance ofa.vcf; its subsequent tokens still use the obsolete epoch. In the #777/#794 replacement, sequence numbering restarts at 1, so this same one-card replacement returns an empty, complete delta under the old token: a silent missed replacement.Fix: validate and issue tokens against the durable epoch, head, floor and rows from one SQLite read snapshot, including
token/ctag; or enforce a single-handle invariant at the storage boundary. Add a reader-held-across-successful-rebuild regression that requires invalid-token/full-resync. Same-handle rebuild tests cannot prove this property.Severity is P2, not P1: the production adapter shares one locked Book per user, so this demonstrates the public multi-handle cache path, not a reproduced failure in the mounted single-Book server. The cache's independent-reader behavior is already exercised in the stacked WAL-read regression. This is a newly identified pre-existing gap, not evidence that the retention fix regressed.
other checks
i64::MAXfails the next write closed; independent probe confirms unchanged cursor/token and no partial row.26edc647leavescrates/dav, adapter, properties, transport/network tests unchanged. Sync's existing aggregate payload/index byte guard remains in addition to retention.verification and limits
Read-only diffs/whitespace checks passed for all three updates. Hash comparison confirms 421/421 Rust/Cargo files in the existing dev-02 retention worktree match
f3d6361d, and 445/445 in the tested dev-01 merged worktree match80ec1fa3. The independent runtime evidence in this round is from the merged tree; the #776-only run remained queued behind occupied dev-02 slots and was cancelled before starting. The base finding above is also visible directly in f3d6361d's unchanged cached-epoch logic. Existing #776 gate reports were inspected, not represented as fresh passes.On dev-01, in
nix develop -c bash -cthrough~/bin/slot:All 13 committed cache, 2 crash-test functions and the memory parent test pass. Independent window/deletion and sequence-exhaustion probes pass; the held-reader probe fails as expected for R2-1. An initial file-size-limit probe hit SQLITE_IOERR rather than FULL; a separate stage-only ENOSPC fault-injection rerun passes cleanup/retry. Logs:
~/review-carddav-r2b.logand~/review-carddav-r2-enospc-b.log. Untracked review probes live only on VMs; no tracked source edits.verdict: fix first, for R2-1. New findings: P0 0, P1 0, P2 1, P3 0. No code pushed, merge or production access.
R2-1 implementation plan (shared #776/#777): remove the per-handle cached epoch. Sync reads epoch/head/floor/rows in one SQLite read transaction, then releases that snapshot and checks the current committed epoch before returning. Token/ctag use the same guard. Old tokens return RFC 6578 invalid-token; a rebuild observed during a read fails closed.
Bounds: constant metadata/epoch work per request, existing bounded sync page and retention costs unchanged; one read transaction, no book-sized copy. No new lock across rebuild staging.
Gates: reproduce held-reader failure first; cache/read tests on #776, inherited tests plus rebuild suites on #777; re-merge #777 into #794 and run requested dav/carddav/auth/server, mount, contract, clippy -D warnings and fmt before pushing #794. No dev merge or additional review round.
R2-1 implementation status: ready for FINAL review
Shared finding from #776#issuecomment-71275 / #777#issuecomment-71278 implemented in the base and propagated in stack order. Heads: #776
113af4f819, #777670a883c93. Re-merged #777 into #794:21e678b258, pushed only after every requested gate passed. Never merged into dev. No additional independent review round started.Removed the per-handle cached epoch. Each sync pins epoch/head/floor/rows in one SQLite read transaction. After releasing that snapshot, it queries the current committed epoch again and discards the result with
InvalidTokenif it changed.tokenandctaguse the same guard. Every emitted sync token carries the epoch read for that snapshot. Existing HTTP mapping returns RFC 6578 403valid-sync-token, followed by empty-token full resync. A rebuild after the final epoch check is ordered after that read; the returned old-epoch token is then rejected on the next request.InvalidToken, issues current token/ctag, and fully resyncs replacement rows. Equal revision counts exercise #777's former silent empty delta.InvalidTokenafter the current committed epoch check.valid-sync-token, then 207 with b.vcf and fresh token.-D warnings; workspace fmt.dav_is_mounted/contract-D warnings; workspace fmt clean.VM: claude@calternal-dev-01-compute02. Worktrees:
~/Developer/Worktrees/carddav-epoch-r2,carddav-epoch-stack-r2,dav794-epoch-r2. All builds/tests/fmt used Nix, with heavy work through one~/bin/slot,-j4. Reused target:/home/claude/Developer/Worktrees/dav794-retention-r1/target; log records recompilation from each checked worktree. Authoritative log~/carddav-epoch-final2.logcontainsBASE_GREEN,STACK_GREEN,TRANSPORT_GREEN;~/carddav-epoch-final2.exit= 0.Exact commands in the Nix shell, inside the slot. On each of #776 and #777:
On the refreshed #794:
Failing-before evidence:
~/carddav-epoch-red.log, baseline407eabf0(original base plus regression), exits 101 because the held reader accepts the obsolete epoch. Two subsequent test-fixture mistakes were corrected: an accidentally changed existing retention expectation, and the HTTP fake source cursor triggering an unrelated second rebuild. The final source above passes all gates; earlier runs are not claimed green.Decisions/limits: constant extra epoch queries and a read transaction; existing bounded page/retention/staging behavior preserved. No new book-sized allocation or rebuild lock. Under repeated concurrent rebuilds a request may fail and require full resync instead of returning stale success. Native Apple replay, perf-VM latency budgets and deterministic kill/power loss inside backup installation remain the previously documented limits; none is claimed here. FINAL review remains pending.
Independent review, round 3 (final)
Reviewed #776 at
113af4f819, the shared round-2 finding (#776#issuecomment-71275 / #777#issuecomment-71278), implementation status (#776#issuecomment-71333 / #777#issuecomment-71338), and propagation into #79421e678b2.verdict: ready. New findings: P0 0, P1 0, P2 0, P3 0. The shared R2-1 finding is closed. No new P0/P1 appeared, so kayg's final-round “stop and simplify” trigger is not met.
valid-sync-token, followed by an empty-token 207 containing replacement b.vcf. No successful empty delta under the obsolete token.InvalidTokenafter the transaction ends and the current committed epoch differs.New findings
None at P0–P3 within this review's scope.
Verification and limits
Fresh independent checks on dev-01, in Nix through
~/bin/slot, reused target/home/claude/Developer/Worktrees/dav794-retention-r1/target:#776: 21 passed, #777: 25 passed, no failures. Both runs include the held-reader cache/HTTP regressions and the deterministic mid-read rebuild test. Logs:
~/review-carddav-r3-final/gates.log(throughTREE_GREEN=carddav-epoch-r2) and~/review-carddav-r3-final/remainder.log(throughTREE_GREEN=carddav-epoch-stack-r2). After those passes, #794's attempted rerun remained queued for a slot and was cancelled before Cargo started; no fresh merged-tree or transport runtime pass is claimed.Independent SHA-256 comparison matches 422/422, 426/426, 446/446 Rust/Cargo files against the specified #776/#777/#794 heads. The #794 VM worktree has an older Git HEAD but its compared source matches
21e678b2. Inspected prior source-matched~/carddav-epoch-final2.logand exit file (0): all three epoch regressions pass on each tree, withBASE_GREEN,STACK_GREEN,TRANSPORT_GREEN; clippy/fmt and broader transport/auth/socket gates are prior implementation evidence, not fresh runs in this review. Read-onlygit diff --checkpasses for f3d6361d..113af4f8, 3c8ddf53..670a883c and 80ec1fa3..21e678b2.Native Apple replay, perf-VM latency gates and interruption inside backup installation remain the documented limits. No new allocation proportional to book size, write lock or transport-policy change was introduced by this fix. No code changed/pushed, merge or production access.
kayg referenced this pull request2026-11-05 23:21:11 +00:00
housekeeping #1009: rebased+green, left open for review.
rebased CardDAV read/cache implementation; used final feature snapshots to reconcile chronological new-crate conflicts. fixed the journal fixture to use the current Caller credential.
head:
17726432f0a4497657dd7981189196a6bf5a4739, based on devcd7f84432febfc63b788fbb9b9f9950452982d74; pushed with the original exact force-with-lease.Mac VM gates passed through Nix, shared Mac lock and slot: frontend frozen install, check, lint, build, bundle gate and unit tests;
cargo fmt -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -- --check;cargo check -j4 --locked -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --all-targets; the same package selection withcargo clippy --all-targets -- -D warningsandcargo test. frontend ran before the Rust-only fixture repair; the Rust run records the pushed head. commands and logs.original-head patch inventory,
pr-776.md.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.