Plan DAV capture/export/replay v3 #818
Open
kayg
wants to merge 1 commit from
docs/dav-recorder-v2-plan into dev
pull from: docs/dav-recorder-v2-plan
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:feat/contrast
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
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: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: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!818
Loading…
Reference in a new issue
No description provided.
Delete branch "docs/dav-recorder-v2-plan"
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?
DAV capture/export/replay plan v3, revised
Design only. This description supersedes
docs/plans/0064-dav-recorder-v2.mdand the earlier v3 description. Architecture accepted; revised against plan review 72220. #820 remains paused at58e503944542a8a84b7e9a6a4389433a877fa099under the three-round rule. No implementation, capture or build is authorized by this design task. Future PRs targetdev; never merge here.Goal and architecture
Capture Apple Contacts ↔ CardDAV traffic with stock Nix mitmdump, export a narrow credential-free typed fixture offline, then reuse #820's Rust replay/comparison. No custom proxy or inline validator. Protocol-valid unknown responses must reach the client unchanged and remain in raw flows; schema errors can only refuse export, retaining raw evidence. Disk, network, TLS and proxy defects remain operational failures: “no schema-induced abort” is not a claim that recording cannot fail.
Evidence: #820 reviews round 1, round 2, round 3. Round 3 caught 23/25 replay differences, yet ordinary responses became 502s and saved fixtures retained Apple auth, Digest nonces and Link URL credentials. Repatching that recorder or building another HTTP proxy repeats the wrong boundary. The small Python exporter uses upstream FlowReader/FlowWriter, not a replacement flow decoder. Reuse existing typed Rust comparison rather than redesigning it.
First PR scope and sizes
Only
contacts-crudandcontacts-initial-sync, Basic app-password authentication, sequential synthetic workloads. Prove on Linux first. Actual Apple profile coverage, Mac GUI access and real-iPhone requirements remain separate PENDING gates.scripts/capture-finalize.py: workload wrapper, final scripted request, graceful stop and private receiptexport/reader.py: file/record preflight and containment; upstream FlowReader -> bounded flowsexport/policy.py,export/bodies.py,export/export.py: Basic/URI/header policy, small card/XML adapters, CLI/atomic writersrc/schema.rs,wire.rs,fixture.rs,main.rs, retainreplay.rsexport/tests/test_export.py,tests/export_contract.rs,tests/review_v3_mutations.rs, focused existing suitesTotal new Python production budget 430–550 lines, plus 100–180 changed Rust lines, 300–400 test lines, 50–70 documentation/Nix lines. These are estimates, not counts or acceptance ceilings. Keep correctness gates if implementation exceeds a budget; revisit decomposition rather than add fallbacks. Nix/dependency closure sizes are not part of these source-line estimates.
export --capture PATH --receipt PATH --profile contacts-v1 --scenario ENUM --macos X.Y.Z --out PATHproduces format 3 JSON for the Rust loader. Fixed metadata enums only; CLI free text cannot enter the fixture. Exit 0 only after complete staged output passes the strict Rust loader, then no-overwrite atomic publication. Export refusal exits 2 with fixed error code, exchange index and schema position, never source values/names/paths/tracebacks. Existing inventorycheckexit 3 remains PENDING until real required captures exist. Reject format 2 and disable/remove old unsaferecord; never silently migrate raw fields.Capture and observable completion
Raw flows and their receipt stay outside all checkouts on the originating VM, directory 0700/files 0600, umask 077. Raw includes secrets: never commit, upload to issues/CI, print, or copy to a repository staging directory. No mapping sidecar or source-valued diagnostics. Use a disposable synthetic account and target-owned credentials read from files, never argv. Stock mitmdump only, configured to retain bodies and avoid host/redirect rewriting. No old schema/body cap on capture. Configure temporary TLS trust only in the disposable guest if required; no production changes.
A clean EOF or sufficient scenario milestones does not establish completion. The private finalizer owns the scripted workload/proxy processes and follows this protocol:
/__dav_capture_complete__/<fresh-random-nonce>, no auth/query/fragment/body. Expect a completed 404 from the unmodified own-server router. This reserved test path needs no product endpoint. Unexpected status, retry, missing response or further scenario traffic prevents finalization. The nonce distinguishes this run; it is not a fixture identifier.This detects a readable prefix cut between complete records, including after apparent scenario coverage but before finalization. It does not turn an arbitrary receipt into proof of actual Contacts behavior. Once a real fixture is accepted and replay evidence recorded, remove local raw/receipt, disposable account and proxy CA material; accurately report deletion without promising storage-level erasure.
Resource boundary before FlowReader
The review identified that mitmproxy 12.2.3 FlowReader calls tnetstring.load, which allocates the record before semantic checks, and can auto-load HAR. Do not claim the 4 MiB body check bounds that allocation.
Concrete importer contract, implemented around the upstream decoder:
read()or mmap. Limit receipt to 4 KiB before parsing..content/.textbefore checking content encoding. Support identity/gzip only; incrementally decompress raw content in bounded chunks with a 4 MiB output ceiling and reject overrun, incomplete stream, unsupported coding or bad data. Do not materialize expansion before checking it. Semantic headers/trailers cannot be silently hidden as framing.Costs: preflight+hash are O(raw bytes), conversion O(B log I) with ordered slot maps, memory O(one bounded decoded flow + bounded slot keys + upstream decoder object overhead). The process cap contains that overhead; exceeding it is an explicit refusal, not a claim of exact Python allocation accounting. Staged files contain only new typed objects; never serialize Flow, Flow state, exception or arbitrary dictionaries from input.
Closed allowlist, contacts-v1
Default for every unlisted request/response header, URI feature, XML QName/attribute, card property/parameter and body variant: refuse offline export. Never copy an unknown name or infer that an unlisted request header is irrelevant. No Raw/Other strings, name substrings, hashes of captured secrets, whole-header opaque fallback or source-to-slot map in output. Every retained scalar is a closed grammar value or a typed slot assigned by first-seen identity. Preserve repeated identity within its semantic domain; distinct UID identities cannot collapse.
/dav, contacts vs addressbook, slash shape and origin. Queries, fragments and userinfo refuse, not scrub. Unknown routes refuse. Target origin translation is explicit; external redirects need an approved disposable target origin binding. Follow/check target-advertised hrefs, never repair them to a working canonical URL.Header names are fixed enums matched case-insensitively; preserve presence, multiplicity and order among same-name values. Ignore different-header ordering. Any unlisted value/name refuses without echoing it.
Body variants: Empty, DavXml, VCard, OmittedErrorBody. Plain/HTML at 4xx/5xx or supported discovery redirects becomes only presence plus fixed media type and an explicit error-prose-omitted comparison flag; no contents, lengths or digests. Successful HTML 200 refuses rather than masquerading as DAV. Unknown successful bodies refuse. Embedded address-data parses as vCard even through XML escaping/CDATA.
XML allowlist starts from the DAV/CardDAV entries only in #820
parse_xml_elementat58e50394, restricted further to the two scripted scenarios and historical discovery/error controls. Record the final exact enum table with those tests, not a wildcard namespace. Fixed supported attributes use checked enums or typed slots; unknown names/attributes refuse. Preserve namespace/node shape/order, typed href, ETag, sync-token, media type/charset, representation length, display-text slots and validated creationdate/getlastmodified. No DTD/entities. Explicit additions: DAV principal-collection-set, report capabilities with their supported-report-set/supported-report/report children, root href, and CardDAV{urn:ietf:params:xml:ns:carddav}no-uid-conflictunder DAV error at 403. Never equate wrong namespaces.The round-3 review did not name the Radicale extension QName. Reproduce it privately on a Linux VM before first-PR gate completion; record a source-backed decision and a minimal synthetic regression. Add a closed typed entry if relevant, or an exact named drop with evidence of irrelevance. Unknown extension refuses until that decision; successful export of the supported discovery reproduction is required. No generic Radicale exemption. Radicale is research-only, not a product or routine regression dependency.
Explicit ancillary drop boundary: Flow connection/client/server addresses, TLS certificates, arbitrary metadata, absolute timestamps/durations, capture IDs and proxy options never enter fixture or diagnostics. Request times may be inspected privately for sequential-order checks only. Do not serialize them. Private receipt hash/nonce and file paths are likewise excluded. Add sentinel tests in each ancillary field.
Reuse and own-DAV evidence
Keep #820 field-by-field comparison, independent content seeds, TargetBindings bijection, structural route checks, SyncProgress, ETag/If-Match and stale-write 412, ordered multistatus namespace/shape, timestamp/charset/length checks, required-scenario inventory and PENDING logic. Retain existing replay limits (4 MiB bodies, nesting 32, 30-second exchange deadline). Reuse focused tests in replay.rs, fixtures.rs, synthetic.rs, review_round1_mutations.rs, review_etag_xml.rs, review_r2_mutations.rs and review_regressions.rs. Strengthen tests that fail on missing seeds before the intended mutation. Port independent dev-02 review820-r3 probes as synthetic tests; do not copy raw logs/captures.
Do not reuse schema.rs raw-header fallback or whole-value redaction, record.rs proxy/inline validation or unconditional cleanup messages. Construct new closed output objects. Delete assertions that ordinary valid source responses should abort capture.
Own-DAV round-3 reproduction: source-derived wire 403 containing CardDAV no-uid-conflict, from
carddav-writes-r2(crates/carddav/src/write.rs:90–95viacrates/dav/src/serve.rs:915–923), caused recorder 502 because it knew only DAV no-uid-conflict at 409. The available carddav-r2 live binary was not DAV-mounted; its well-known HTML 200 proves no live own-DAV compatibility. New gate must drive a disposable DAV-mounted own server through stock mitmdump on Linux: discovery, initial 401, Basic authenticated depth-0 PROPFIND, two independently seeded contacts, duplicate-UID write yielding exact 403, then successful read/recovery and completion sentinel. Capture must retain that response; export/replay must check status/QName/href. Attach sanitized commands, mounted route and source head only. An unmounted HTML 200 fails; a wire stub alone is insufficient.Failure table
First-PR Linux gates and historical inventory
All automated first-PR gates run on Linux VMs, not the control host or Mac. They do not depend on GUI permission. Use stock mitmdump with a disposable DAV-mounted Calternal server for product integration, scripted HTTP clients for both scenarios, and upstream HTTPFlow/FlowWriter for focused exporter cases. Minimal synthetic responders may mutate or serve historical response shapes through the stock proxy; no custom production recorder. Real Apple coverage stays PENDING.
Every response mutation starts with an independently seeded passing HTTP replay baseline. Change one semantic value, keep Date/Content-Length controls and actual PUT/If-Match flows, and assert intended rejection code/exchange. Missing-seed failures or tests that assert historical misses do not count as mutation detection.
/, HTTP/HTTPS Location/Content-Location, 401 text/plain, depth-0 207 principal/report/root discovery including exact reproduced Radicale extension, and own-DAV CardDAV no-uid-conflict 403 reach client unchanged, export and replay for supported shapes. Own-server mounted gate is mandatory; unknown XML refuses offline.Additional mandatory gates:
Nix and commands for implementation
The reviewer evaluated nixpkgs pin
1e7da2489c0ba3a06bceec1b444fbc02c5223ab1: Python 3.14.7/mitmproxy 12.2.3 available on x86_64-linux and aarch64-linux. That is evaluation evidence only, not build/import proof. Providepkgs.python3.withPackages (ps: [ ps.mitmproxy ])and mitmdump from that same package set. PATH executable alone is insufficient. No pip or alternate decoder. Temporary Nix files follow BRIEF-COMMON until #779 merges; never commit the temporary overlay.On both dev-01 and dev-02, through
netbird sshand Nix, require import ofFlowReader, FlowWriter, a synthetic writer→reader round trip and stock mitmdump smoke test. On dev-02 run focused functional suites and mounted own-server scenario through~/bin/slot, with production builds at-j4. Planned commands (not results):nix develop -c bash -c '~/bin/slot python3 -m unittest discover -s crates/dav-replay/export/tests'(includes package import, writer/reader, mitmdump integration, finalization/resource/privacy gates).nix develop -c bash -c 'cargo fmt --all --check'.nix develop -c bash -c '~/bin/slot cargo clippy -j4 -p calternal-dav-replay --locked -- -D warnings'.nix develop -c bash -c '~/bin/slot cargo test -j4 -p calternal-dav-replay --locked'(includes v3 contract, typed historical mutation matrix).nix develop -c bash -c '~/bin/slot cargo run -j4 -p calternal-dav-replay --locked -- check crates/dav-replay/fixtures'must retain exit 3/PENDING until real captures exist.Use the existing VM job slots; stop only owned processes. Capture/finalization integration failures fail the first-PR gate, not merely a pending native-capture result. A real Mac capture later requires the Mac lock/Nix/GUI permissions; Linux scripted traffic must not be labeled Apple capture. Calendar/Reminders and real-iPhone gates remain visible. ADR 0067 traffic storage stays on the issue, not in the repository; this contacts-only plan does not authorize calendar fixtures.
Follow-ups and unresolved evidence
Separate reviewed follow-ups, only when source evidence requires them: Digest/Bearer and target challenge/state handling; cookies with typed names/values/flags and target-owned jar (then Secure-removal comparison); Link relations; URI userinfo/query/fragment policy; additional card fields/parameters/NOTE/photo/vCard versions; Calendar/Reminders profile; concurrent replay. None is a hidden first-PR dependency: unsupported sessions refuse without publication.
First-PR owner must establish the exact Radicale QName and mounted own-server head/start/seed commands, preserving the historical tests. Default: export refusal / product gate fails, never silently ignore. Real Mac routing/TLS trust/GUI access and whether actual Contacts needs extra auth/fields remain pending source evidence. The narrow profile does not claim full Apple coverage. No code changes, builds, tests or captures were run for this plan revision.
Plan review
Blocking
Should change
statusandtyped bodywording implies this but does not make the invariant explicit.Nits
Verdict: approve with changes.
Plan typed DAV recorder v2to Plan DAV capture/export/replay v3Independent plan review (v3)
Reviewed #818 at
dfcd2fdfa577d58a9153760577dcd30c2c45fb1f, its description, and all three #820 reviews: 72055, 72126, 72191. Verdict: revise, preserving the proposed three-part architecture.Structural assessment
The architecture removes both repeated mechanisms. Stock mitmdump has no application schema validation that can turn an ordinary DAV response into a 502. Unsupported semantics now stop offline export while preserving capture. Proxy setup, disk failures and upstream proxy defects remain separate operational failures.
For fixtures, closed names plus grammars/typed slots remove the raw-header fallback. The plan explicitly covers free text in allowed headers, XML text/attributes/CDATA, vCard NOTE and parameters, URL userinfo/query/path, Digest material, and error prose. PHOTO/binary content refuses export. No remaining arbitrary-text copy path is authorized by the design. This conclusion depends on constructing new output objects rather than serializing any Flow state or reusing #820's raw variants. Sentinel tests support that boundary; they do not establish it alone.
Findings P0–P3
P0: none found.
P1: capture completeness has no observable completion contract. The plan promises that interrupted saves/timeouts cannot publish a complete scenario, but a
.mitmfile is a sequence of independent records. FlowReader accepts EOF between records. A proxy stopped after a complete flow, with a later request pending or not yet saved, can leave a readable prefix containing all currently required scenario milestones. No error flow or parse failure need survive. Required-scenario checks alone cannot distinguish that prefix from a finished session. Specify a small external capture-finalization protocol: finish the workload, drain/stop the proxy successfully, then create a private completion receipt tied to the exact raw file; export requires that evidence plus scenario checks. No addon or custom flow format is needed. Missing/failed finalization must retain raw evidence and refuse publication. Gate: stop capture at a complete-record boundary after apparent scenario coverage but before successful finalization; export must refuse. Do not promise detection from FlowReader alone.P2: the stated resource bound starts after the dependency has allocated the flow. In pinned mitmproxy 12.2.3, FlowReader calls tnetstring.load; tnetstring.load reads the advertised complete record into memory before constructing the Flow. FlowReader also auto-detects HAR and loads that whole document. A 4 MiB decoded-body check and flow/slot caps do not bound that allocation, headers, or imported metadata. Revise the memory claim and put a concrete raw-input/file limit and process memory/time containment before FlowReader, plus bounded decompression before materializing decoded bodies. Keep the upstream decoder. Gate oversized single records/headers, HAR input, and compressed expansion; failures must leave no published fixture or source-valued traceback.
P2: the first slice is larger than these two scenarios require. The 800–1,000 Python / 450–650 test line budgets are estimates, not evidence that this is the simplest implementation. Make the cuts below explicit before implementation; retain the closed contract and regression coverage.
P3: explicitly exclude Flow timing and ancillary state. Say that connection addresses, TLS certificates, Flow metadata, timestamps/durations and capture IDs never enter v3 or diagnostics. Request times may be used privately for ordering checks. Ordering must not serialize absolute times or durations. The existing closed-output rule implies this; an explicit drop rule and sentinel checks make the boundary reviewable.
Concrete first-PR cuts
These cuts trade breadth for explicit unsupported-session refusal. They do not relax capture survival or fixture privacy. Keep discovered Contacts-required fields, the exact Radicale extension decision, legacy dates, CardDAV 403, ordering, seed/bijection checks, limits and publication tests. Do not cut those to meet a line budget.
Test feasibility
Every historical P1 can be automated on Linux without a Mac capture. Use mitmproxy HTTPFlow/FlowWriter for focused exporter cases and stock mitmdump against minimal synthetic HTTP servers for capture integration. Drive a disposable DAV-mounted Calternal server through mitmdump for the product integration gate. A real Apple session remains a separate PENDING gate.
/davor change collection route; replay must reject/fail the actual advertised route without repair.Keep passing seeded baselines before every response mutation, and assert its rejection code/exchange. Synthetic Linux coverage can prove these regressions independently of Apple GUI access; it cannot prove that the chosen profile covers actual Apple traffic.
Nix packaging evidence and limits
No packaging blocker found. At the prescribed nixpkgs pin
1e7da2489c0ba3a06bceec1b444fbc02c5223ab1, mitmproxy is a buildPythonPackage, version 12.2.3; the top-level executable package is itstoPythonApplicationwrapper. Usepkgs.python3.withPackages (ps: [ ps.mitmproxy ])for the exporter, with the executable from the same package set. Puttingpkgs.mitmproxyon PATH alone is insufficient.Read-only
nix eval --json --impure --expr …vianetbird sshon dev-01 x86_64-linux and dev-02 aarch64-linux successfully evaluated that Python environment'sdrvPath; both report Python 3.14.7, mitmproxy 12.2.3, andlib.meta.availableOntrue for mitmproxy and mitmproxy-rs. This is evaluation evidence, not build/import proof. First implementation gates should runfrom mitmproxy.io import FlowReader, FlowWriterplus a FlowWriter→FlowReader round trip and mitmdump smoke test on both architectures. No pip or alternate decoder is needed.No implementation, builds, test runs, commits or pushes were performed in this review. Only package evaluation ran on the VMs. Revise the completion contract, pre-reader resource boundary and first-slice scope; then the architecture is suitable to implement.
Revised plan v3 in the description against review 72220:
New estimated budget: 430–550 Python production lines, 100–180 changed Rust lines, 300–400 new/adapted test lines, plus 50–70 documentation/Nix lines. Estimates do not waive correctness gates. No code, builds, tests or captures changed/run; #820 remains paused.
GUI prerequisites on
macos-01-stdare done (2026-11-01, IST morning):mkcert.sh), and slice 0 used a public wildcard cert. Trusting needs no GUI:~/bin/trust-test-ca.sh <ca.pem>on the Mac (sudo security add-trusted-cert -d -r trustRoot -p ssl -k /Library/Keychains/System.keychain). Proved with a throwaway CA: verify-cert NOT_TRUSTED before, "successful" after; removed again afterwards. Remove a run's CA at the end withsudo security remove-trusted-cert -d ca.pem.Screen Sharing is enabled on the VM. Reconnect: GUI clicks are easiest through the guest's cua-spacesd (MCP at 127.0.0.1:3211, token in ~/.cua/spacesd/token). The VNC framebuffer showed black, so VNC is not useful here.
kayg referenced this pull request2026-11-05 23:21:11 +00:00
housekeeping #1009: rebased+green, left open for review.
rebased DAV recorder plan; proposal retained.
head:
5347df21aab3fe9311fd1b37f2e8c5c3c69f941a. rebased onto devcd7f84432febfc63b788fbb9b9f9950452982d74and pushed with the original exact force-with-lease.validation on the Mac VM, through Nix and the shared Mac lock/slot: root
bun install --frozen-lockfile; inapps/web,bun run check,bun run lint,bun run build,bun run test;PERF_DIST=<worktree>/apps/web/dist bash perf/web.sh --bundle-only --gate. all passed. this documentation-only branch has no targeted browser specs or Rust implementation changes. exact commands, logs, and dev bundle control.original-head missing-commit/patch inventory, file
pr-818.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.