DAV: shared scoped transport with CardDAV mounted in the server #794

Open
kayg wants to merge 9 commits from feat/dav-transport into issue/carddav-read-perf-fix-r2
Owner

Stack refresh: cache review round 1 fixes, gates green

Head 80ec1fa3 includes updated #777 3c8ddf53 and #776 f3d6361d. Integration status table, exact gates and limits. All requested transport/package, mount, contract, clippy and fmt gates passed before push. Never merged into dev; refreshed-stack independent review remains pending.

Round 2 fix, final review pending

Implementation plan and failure table: #794 (comment). Response admission follows owner-backed Bytes through Hyper's socket queue. The DAV listener preserves queued/vectored writes, including a scalar-I/O fallback, instead of allowing Hyper to flatten replies into uncharged buffers. Default budgets: 64 MiB total reply reservations, 8 MiB per request, at most 8 admitted requests under that byte budget, still at most 4 per owner and an upper count ceiling of 64. Reserve before buffering/readiness/allocation, refuse immediately with 429. O(body + reply) work and bounded request/byte state; transient serializer/store buffers remain separately bounded by the existing per-reply limits.

Default 60 s total from handler entry through body read, readiness/handling and response socket writes; 10 s idle between request-body frames and while response writes are pending. Configurable through calternal_dav::Config. Tower-http supplies handler and body-read timeouts; the DAV-aware listener handles socket write idle and total expiry because body middleware cannot cover already-polled Hyper frames. Expiry closes the connection. Running blocking closures keep admission and byte reservations until they actually end. On HTTP/2, expiry closes the shared connection, so other streams retry too.

Failure Effect and detection Recovery Regression
Slow TCP readers Byte owners retain owner/global permits; queued write path preserves them Disconnect, write idle or total deadline drops connection and bytes Eight real authenticated CardDAV TCP readers; clone/slice ownership; independent idle/total and disconnect recovery
Global reply pressure Pre-work maximum-size reservations bound serialized in-flight replies Immediate 429, retry after a reservation returns Byte-budget unit test across owners and TCP probe below the per-owner cap
Stalled/dripping upload or readiness Tower body idle / handler total timeout Close connection; fresh requests regain admission Real TCP stalled uploads and periodic body progress; held readiness deadline
Expired/cancelled blocking work Work stays charged in its closure Admission returns when the closure ends Timeout and cancellation blocking-read regressions

Round-2 fixes pushed at 9e95ad41a0. Final dev-01 gates: 264 package tests passed, 7 existing ignores; mount, contract, shutdown I/O, clippy -D warnings and fmt passed. Full status table, exact commands and evidence. All 1,198 source files match the tested isolated VM tree. Independent FINAL review round 3 remains pending. Never merged. The original gate record below is historical at 9a6c56a8.

Plan, bounds, and failure table

Shared DAV transport, with CardDAV as adapter #1, landing after #777 and #782, into dev. Re-merged origin/feat/dav-app-passwords through a59f782e before the round-1 fixes. Never merge automatically. Refs #657.

The transport owns credential-scoped discovery, raw-path validation, PROPFIND/REPORT/GET/HEAD/OPTIONS, XML, bounded pages and conditional reads. CardDAV owns its journal-fed projection. A CalDAV-shaped adapter proves reuse; actual CalDAV conversion, calendar-query, recurrence and writes remain ADR 0067 steps 7–9.

Hard bounds, enforced before the corresponding allocation/work:

  • 1 MiB HTTP body before parsing. Allocation-free lexical XML preflight before a namespace-aware limited tree: depth 32, 20,000 nodes, 4 KiB tags, no DTD/custom entity expansion. At most 1,000 multiget hrefs.
  • Namespace/name/attribute text at most 1 KiB each; owned selection text at most 64 KiB and 256 distinct qualified properties. Equal duplicates are deduplicated; conflicting duplicate selectors refuse with 400.
  • At most 1,000 resources per reply (PROPFIND includes the collection; the RFC 6578 overflow marker is additional), plus 8 MiB serialized bytes. Checked builders refuse overflow with a small 507 protocol error and no partial success/token. Retained CardDAV sync payloads (body plus index) also cap at 8 MiB, with correct continuation paging.
  • 64 admitted requests per router, 4 per credential owner, across adapters, before body buffering/readiness. Immediate 429 + Retry-After on overload. Admission survives blocking cancellation and stays attached to unread response bodies. At most 32 blocking closures run; their finite waiters are already admitted.
  • Principal parsing/rendering and store reads run on the blocking pool. Projection readiness stays asynchronous. Adapter namespace bindings are checked at router construction; CardDAV uses c, the calendar stub cal.

Sizes and complexity: XML preflight/tree parsing O(body); selection copies are charged before ownership and unique-property comparisons are bounded by 256 properties; metadata pages O(log n + page), GET O(log n + bytes), multiget O(hrefs × log n + bounded bytes), sync one bounded payload page. Admission scans at most 64 live user buckets and drops dead buckets. Existing O(n) cold projection rebuild staging remains inherited, separate from request XML/reply bounds.

Failure Effect Detection Recovery Gate
Wrong/revoked/wrong-scope credential or foreign/odd path No adapter read Scoped gates and canonical credential owner 401/404; cold overload 429 Auth/transport attack matrices and server mount
Long inherited namespace, duplicate data properties, deep XML, DTD or excess hrefs Bounded parse/selection/reply work Byte/node/depth/name/selection/href budgets 400/413 or bounded 507 Reviewer probes, real CardDAV duplicates and isolated peak-RSS regression
Cold auth flood Finite pending DB admission #782 pending try-acquire before FIFO 429; warm-cache bypass Blocked-DB admission, timeout and cancellation test
Slow readiness/read or unread replies Finite retained requests Router/user admission before buffering Immediate 429; permits return on drop/completion Held store, oversized overload body, cancellation and slow-reader tests
Projection home-open/readiness failure No success hiding a failed home Explicit None/error handling 503, then retry Principal/direct-home failures followed by recovery
Namespace prefix conflict No invalid mixed-adapter XML Router construction validation Assign a distinct prefix Real CardDAV + calendar Depth 0/1 namespace-aware discovery
Resource/serialized-byte overflow No silent truncation or false sync completion Page and checked-output limits Continuation marker/token, or 507 requiring smaller request 1,000-resource tests, byte-limited sync pages and aggregate response overflow
Invalid sync token / stale ETag No false continuity or stale read Adapter token validation / shared preconditions 403 valid-sync-token / 412 or 304 Existing CardDAV and transport suites

Gates

All passed on dev-01 in the Nix shell through one ~/bin/slot, at the exact source tree committed as 9a6c56a86f. Final target directory is isolated: /home/claude/Developer/Worktrees/dav794-r1/target.

cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast
cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked
cargo test -j4 -p calternal --bin calternal contract --locked
cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings
cargo fmt --all -- --check

Package gate: 254 passed, 0 failed, 7 existing ignores (DAV 26, CardDAV 26, auth 179, server 23). Mount and contract: one test each passed. Clippy -D warnings and fmt clean. Explicit memory rerun also passed: cargo test -j4 -p calternal-dav --test memory_bound --locked -- --nocapture. The hostile 68,680-byte authenticated request measured 12,884 KiB peak RSS, 88 KiB growth, under assertions of 64 MiB absolute and 8 MiB growth, with a bounded 400 reply. Log: ~/dav794-r1-verified.log, ending GATES_EXIT=0.

Compared SHA-256 for all 1,196 source files between control and tested VM worktrees: identical, excluding VM-only Nix shell files. No Nix files committed. Earlier shared-target baseline/fixed binary reuse was detected; that sample was discarded and the complete final gates rerun in the isolated target directory.

Decisions and open risks

All five round-1 findings are fixed; none deferred. Detailed findings/status and failing-before evidence are in the round-1 implementation comment. Independent review of the new head remains pending; the maximum is three review rounds.

Byte-overflow replies deliberately refuse rather than send partial XML or a misleading sync token; clients must reduce href counts/nresults. Future adapters must choose globally distinct namespace prefixes. Percent escapes and odd/raw path spellings remain fail-closed; URLs never select storage identity. Inherited cache compaction/cold rebuild staging, native Apple replay and perf-VM latency gates remain later work and are not claimed here. This PR remains read-only and unmerged.

## Stack refresh: cache review round 1 fixes, gates green Head **80ec1fa3** includes updated #777 **3c8ddf53** and #776 **f3d6361d**. [Integration status table, exact gates and limits](https://git.kayg.org/kayg/calternal/pulls/794#issuecomment-71242). All requested transport/package, mount, contract, clippy and fmt gates passed before push. Never merged into dev; refreshed-stack independent review remains pending. ## Round 2 fix, final review pending Implementation plan and failure table: https://git.kayg.org/kayg/calternal/pulls/794#issuecomment-70985. Response admission follows owner-backed Bytes through Hyper's socket queue. The DAV listener preserves queued/vectored writes, including a scalar-I/O fallback, instead of allowing Hyper to flatten replies into uncharged buffers. Default budgets: **64 MiB total reply reservations, 8 MiB per request, at most 8 admitted requests under that byte budget**, still at most 4 per owner and an upper count ceiling of 64. Reserve before buffering/readiness/allocation, refuse immediately with 429. O(body + reply) work and bounded request/byte state; transient serializer/store buffers remain separately bounded by the existing per-reply limits. Default **60 s total** from handler entry through body read, readiness/handling and response socket writes; **10 s idle** between request-body frames and while response writes are pending. Configurable through `calternal_dav::Config`. Tower-http supplies handler and body-read timeouts; the DAV-aware listener handles socket write idle and total expiry because body middleware cannot cover already-polled Hyper frames. Expiry closes the connection. Running blocking closures keep admission and byte reservations until they actually end. On HTTP/2, expiry closes the shared connection, so other streams retry too. | Failure | Effect and detection | Recovery | Regression | |---|---|---|---| | Slow TCP readers | Byte owners retain owner/global permits; queued write path preserves them | Disconnect, write idle or total deadline drops connection and bytes | Eight real authenticated CardDAV TCP readers; clone/slice ownership; independent idle/total and disconnect recovery | | Global reply pressure | Pre-work maximum-size reservations bound serialized in-flight replies | Immediate 429, retry after a reservation returns | Byte-budget unit test across owners and TCP probe below the per-owner cap | | Stalled/dripping upload or readiness | Tower body idle / handler total timeout | Close connection; fresh requests regain admission | Real TCP stalled uploads and periodic body progress; held readiness deadline | | Expired/cancelled blocking work | Work stays charged in its closure | Admission returns when the closure ends | Timeout and cancellation blocking-read regressions | Round-2 fixes pushed at **9e95ad41a04b0d3d0bf3ffed84b0d67848b46954**. Final dev-01 gates: **264 package tests passed, 7 existing ignores; mount, contract, shutdown I/O, clippy -D warnings and fmt passed**. [Full status table, exact commands and evidence](https://git.kayg.org/kayg/calternal/pulls/794#issuecomment-71076). All 1,198 source files match the tested isolated VM tree. Independent **FINAL review round 3 remains pending**. Never merged. The original gate record below is historical at 9a6c56a8. ## Plan, bounds, and failure table Shared DAV transport, with CardDAV as adapter #1, landing **after #777 and #782**, into `dev`. Re-merged `origin/feat/dav-app-passwords` through **a59f782e** before the round-1 fixes. Never merge automatically. Refs #657. The transport owns credential-scoped discovery, raw-path validation, PROPFIND/REPORT/GET/HEAD/OPTIONS, XML, bounded pages and conditional reads. CardDAV owns its journal-fed projection. A CalDAV-shaped adapter proves reuse; actual CalDAV conversion, calendar-query, recurrence and writes remain ADR 0067 steps 7–9. Hard bounds, enforced before the corresponding allocation/work: - 1 MiB HTTP body before parsing. Allocation-free lexical XML preflight before a namespace-aware limited tree: depth 32, 20,000 nodes, 4 KiB tags, no DTD/custom entity expansion. At most 1,000 multiget hrefs. - Namespace/name/attribute text at most 1 KiB each; owned selection text at most 64 KiB and 256 distinct qualified properties. Equal duplicates are deduplicated; conflicting duplicate selectors refuse with 400. - At most 1,000 resources per reply (PROPFIND includes the collection; the RFC 6578 overflow marker is additional), plus **8 MiB serialized bytes**. Checked builders refuse overflow with a small 507 protocol error and no partial success/token. Retained CardDAV sync payloads (body plus index) also cap at 8 MiB, with correct continuation paging. - **64 admitted requests per router, 4 per credential owner**, across adapters, before body buffering/readiness. Immediate 429 + Retry-After on overload. Admission survives blocking cancellation and stays attached to unread response bodies. At most 32 blocking closures run; their finite waiters are already admitted. - Principal parsing/rendering and store reads run on the blocking pool. Projection readiness stays asynchronous. Adapter namespace bindings are checked at router construction; CardDAV uses `c`, the calendar stub `cal`. Sizes and complexity: XML preflight/tree parsing O(body); selection copies are charged before ownership and unique-property comparisons are bounded by 256 properties; metadata pages O(log n + page), GET O(log n + bytes), multiget O(hrefs × log n + bounded bytes), sync one bounded payload page. Admission scans at most 64 live user buckets and drops dead buckets. Existing O(n) cold projection rebuild staging remains inherited, separate from request XML/reply bounds. | Failure | Effect | Detection | Recovery | Gate | |---|---|---|---|---| | Wrong/revoked/wrong-scope credential or foreign/odd path | No adapter read | Scoped gates and canonical credential owner | 401/404; cold overload 429 | Auth/transport attack matrices and server mount | | Long inherited namespace, duplicate data properties, deep XML, DTD or excess hrefs | Bounded parse/selection/reply work | Byte/node/depth/name/selection/href budgets | 400/413 or bounded 507 | Reviewer probes, real CardDAV duplicates and isolated peak-RSS regression | | Cold auth flood | Finite pending DB admission | #782 pending try-acquire before FIFO | 429; warm-cache bypass | Blocked-DB admission, timeout and cancellation test | | Slow readiness/read or unread replies | Finite retained requests | Router/user admission before buffering | Immediate 429; permits return on drop/completion | Held store, oversized overload body, cancellation and slow-reader tests | | Projection home-open/readiness failure | No success hiding a failed home | Explicit None/error handling | 503, then retry | Principal/direct-home failures followed by recovery | | Namespace prefix conflict | No invalid mixed-adapter XML | Router construction validation | Assign a distinct prefix | Real CardDAV + calendar Depth 0/1 namespace-aware discovery | | Resource/serialized-byte overflow | No silent truncation or false sync completion | Page and checked-output limits | Continuation marker/token, or 507 requiring smaller request | 1,000-resource tests, byte-limited sync pages and aggregate response overflow | | Invalid sync token / stale ETag | No false continuity or stale read | Adapter token validation / shared preconditions | 403 valid-sync-token / 412 or 304 | Existing CardDAV and transport suites | ## Gates All passed on dev-01 in the Nix shell through one `~/bin/slot`, at the exact source tree committed as **9a6c56a86f4123e24d7c9d451b7d332514030ed2**. Final target directory is isolated: `/home/claude/Developer/Worktrees/dav794-r1/target`. ```sh cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked cargo test -j4 -p calternal --bin calternal contract --locked cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings cargo fmt --all -- --check ``` Package gate: **254 passed, 0 failed, 7 existing ignores** (DAV 26, CardDAV 26, auth 179, server 23). Mount and contract: one test each passed. Clippy `-D warnings` and fmt clean. Explicit memory rerun also passed: `cargo test -j4 -p calternal-dav --test memory_bound --locked -- --nocapture`. The hostile 68,680-byte authenticated request measured **12,884 KiB peak RSS, 88 KiB growth**, under assertions of 64 MiB absolute and 8 MiB growth, with a bounded 400 reply. Log: `~/dav794-r1-verified.log`, ending `GATES_EXIT=0`. Compared SHA-256 for all **1,196** source files between control and tested VM worktrees: identical, excluding VM-only Nix shell files. No Nix files committed. Earlier shared-target baseline/fixed binary reuse was detected; that sample was discarded and the complete final gates rerun in the isolated target directory. ## Decisions and open risks All five round-1 findings are fixed; none deferred. Detailed findings/status and failing-before evidence are in the round-1 implementation comment. Independent review of the new head remains pending; the maximum is three review rounds. Byte-overflow replies deliberately refuse rather than send partial XML or a misleading sync token; clients must reduce href counts/nresults. Future adapters must choose globally distinct namespace prefixes. Percent escapes and odd/raw path spellings remain fail-closed; URLs never select storage identity. Inherited cache compaction/cold rebuild staging, native Apple replay and perf-VM latency gates remain later work and are not claimed here. This PR remains read-only and unmerged.
kayg added 20 commits 2026-10-31 16:24:55 +00:00
Add journal-backed CardDAV cache and read bridge
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
49ccd9a338
Reduce CardDAV cache rebuild and cold read I/O
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
370d170e1b
web: regenerate action types
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
70ccd57cb9
Fail-closed DAV URL user check, credential-first throttling, step-up for
minting, bounded cache with 503 on database errors, and small hardening.
# Conflicts:
#	crates/auth/src/app_password.rs
Finish scoped DAV transport gates and bounded CardDAV reads
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
26936341e0
Author
Owner

Independent review, round 1

Reviewed 26936341e0. Read the common brief, ADRs 0064/0067/0068, this PR's plan/failure table, all five #782 review/status comments, and the transport, CardDAV adapter/projection/cache, authentication changes and server mount. Defensive review of our own code. No production code changed, committed or pushed.

Verdict: fix first. P0: 0; P1: 1; P2: 4; P3: 0. One P2 is the still-open prerequisite from #782's final review, explicitly distinguished below.

Findings

R1-1 — P1: byte/node limits do not bound XML selection allocations or response expansion

Location: crates/dav/src/xml.rs:113, also lines 118, 207–215 and 264–280; principal call sites crates/dav/src/serve.rs:530 and :589.

A valid app password can submit a PROPFIND with one long inherited namespace and many <x:a/> properties. selection copies the entire resolved namespace into every property, then respond echoes that namespace declaration for every unknown property. Both happen on the async worker for principal requests. Neither the 1 MiB input cap, 20,000-node cap nor resource-count cap bounds the resulting allocation usefully.

Small VM reproduction against the real router: an 11,336-byte request, 512 properties, an 8,196-byte namespace and Depth 0 produced 4,196,352 bytes of owned namespace strings and a 4,207,909-byte 207 response. No stored contacts are needed. Scaling the same accepted shape to a 512 KiB namespace and 19,000 property nodes stays below both input limits while demanding roughly 9.3 GiB just for namespace copies, before response strings. This is an allocation-derived exhaustion scenario; I deliberately did not run an OOM-sized request. Repeated address-data properties can additionally multiply returned card bytes, beyond the PR's stated one-copy-per-card 250 MiB estimate.

Fix: enforce namespace/name length and aggregate selection-allocation budgets before cloning; deduplicate requested qualified properties; impose a checked serialized-byte budget covering every property/resource, including unknown-property echoes and repeated body properties. Return a bounded protocol error on overflow. Keep principal parsing/serialization off async workers as well. Add the inherited-long-namespace and duplicate-address-data attacks below the byte/node caps; assert bounded allocation/output and prompt rejection.

R1-2 — P2: #782's final required cold-auth admission fix is absent at the mounted head

Location: crates/auth/src/app_password.rs:264, crates/auth/src/lib.rs:133, and the new mount at crates/calternal/src/app.rs:87.

This is the existing #782 R3-1 prerequisite, not a newly discovered auth issue or a fourth auth review. The final review explicitly requires it before mounting. Head 26936341 still has only the eight worker permits and timeout(COLD_WAIT, acquire_owned()); there is no separate pending-admission cap. Arbitrarily many fresh plausible junk credentials can join the 250 ms queue. Source exhaustion changes the post-lookup result rather than admission. This PR exposes that code publicly while claiming the final review fixes were incorporated.

Fix: carry the required bounded pending-cold admission budget into the landing dependency/head; acquire it with try_acquire_owned before the FIFO wait, retain it through the operation and preserve warm-cache bypass. Prove immediate excess rejection and permit release on timeout/cancellation with a blocked DB worker. Existing finite-burst auth tests do not prove a bound on retained pending requests. Retaining the prior P2 severity; no measured OOM claim.

R1-3 — P2: the transport's new blocking budget also leaves an unbounded retained-request queue

Location: crates/dav/src/serve.rs:171, with body buffering/copying at lines 144 and 161 and readiness waits at 303–312.

The 32 permits bound running closures, not requests waiting for a permit. Each authenticated request has already buffered up to 1 MiB and can also retain the body.to_vec() copy while waiting. A slow cache/read, or one user's serialized projection barrier, can retain an arbitrary number of these requests; the blocking-permit wait has no timeout either. Invalid XML/Depth is only parsed after readiness and blocking admission, so rejected work can occupy this queue too. This is separate from the anonymous auth queue in R1-2: a single valid contacts credential reaches it repeatedly.

Fix: add a finite request-admission budget before buffering/readiness, with immediate bounded overload responses and cancellation-safe ownership. Keep the existing running-work permit inside the blocking closure. Validate cheap protocol constraints before store initialization where possible. Test more concurrent requests than the admission cap against a held store/barrier, and verify immediate rejection, bounded retained admissions and cancellation release. The current ticker test proves offloading, not admission bounds.

R1-4 — P2: principal discovery silently treats a failed projection as an absent home

Location: crates/dav/src/serve.rs:597.

For authenticated Depth-1 principal discovery, Ok(Some(h)) if h.ready().await.is_ok() falls through to Ok(_) => {} when readiness fails. The reply is a successful 207 with the home silently omitted. The same failure on a direct home request correctly returns 503. An adapter home() error on discovery instead gets 500, also inconsistent with the direct-home 503 policy. A temporary broken projection therefore looks like a missing collection during discovery, contrary to the failure table's retryable-503 contract.

VM fault injection confirmed principal=207, one principal response only; direct home=503. Neither response leaked the injected private error text.

Fix: distinguish None from readiness failure; propagate home-open/readiness failures as a bounded 503 response consistently. Add HTTP tests for both failure points on principal Depth 1 and direct-home requests, followed by recovery. The cache recovery test only proves cursor/data retention and does not exercise these HTTP paths.

R1-5 — P2: actual CardDAV namespaces collide with the CalDAV stub in shared discovery

Location: crates/dav/src/serve.rs:558, crates/dav/src/xml.rs:222, crates/carddav/src/adapter.rs:20, crates/dav/tests/common/mod.rs:141 and :318.

Principal namespace aggregation deduplicates whole (prefix, URI) pairs. Real CardDAV declares c for CardDAV, while the calendar stub declares c for CalDAV. With both mounted and a credential granting both scopes, discovery emits two different xmlns:c attributes on the same element, making the entire 207 response invalid XML. This is a shared-adapter composition bug; the current production mount has only CardDAV, so its single-adapter discovery does not trigger it.

A VM probe using the exact real CardDAV namespace specification alongside the existing calendar stub produced 207 + DuplicatedNamespace("c", …) from roxmltree. The existing two-adapter test misses this because StubCards declares namespaces: &[] instead of the real adapter's namespaces.

Fix: give adapters globally distinct prefixes and validate conflicts when constructing the router, or scope each adapter's XML declarations locally. Add a coexistence test using the real CardDAV adapter and calendar stub, with dual-scope credentials and namespace-aware parsing of Depth 0/1 discovery.

Failure-table test audit

Row What the existing tests actually establish
Missing/wrong/revoked/wrong-scope credential Real app-password transport checks prove denial and challenge; auth suite covers throttling. They do not establish bounded cold waiters: R1-2. Calendar-to-real-CardDAV item denial is not exercised by the contacts stub, though the production scope selection is correct by inspection.
B URL / odd raw paths Matrix proves refusal and no successful resource response for tested spellings. The gate and transport do not decode; production home(user) receives the credential owner. Tests do not count adapter calls, so the literal “no adapter lookup” claim is supported by code flow rather than a lookup assertion.
Principal scope bypass Single-scope Depth-1 tests prove ungranted home hrefs are absent; per-mount checks preserve adapter scope. Dual-scope tests use an unrealistically empty contacts namespace specification and miss R1-5.
Oversized XML / DTD / excess hrefs Correct-root internal-DTD, body-byte, node and 1,001-href tests prove their rejection paths. They miss legal-shape allocation/serialization amplification: R1-1. The external-entity test uses REPORT with a PROPFIND root, so its broad client-error assertion would also pass for the wrong report root. Explicit allow_dtd: false is the actual XXE protection.
Too many members/changes Stub tests count 1,000 resource results plus the documented extra marker and verify a continuation page. PROPFIND's collection counts toward 1,000; real metadata SQL has LIMIT. These tests establish count limits, not serialized-byte or retained-request bounds.
Stale If-Match GET/HEAD tests prove 412, weak If-Match rejection and 304 behavior; helper tests cover create/update preconditions and repeated headers. No writes ship, so these do not claim mutation-lock correctness. Missing GET targets return 404 before preconditions, consistent with ordinary request-error precedence.
SQLite/cache stall Current-thread ticker and projection-thread assertions prove session reads and refresh leave the async worker available. They do not exercise 32 concurrent jobs, cancellation ownership or overload admission: R1-3. The closure does retain its active permit through completion by inspection.
Invalid/foreign/expired sync token Transport proves malformed-token mapping; real cache tests prove foreign-user and old-generation rejection and stable continuation through changes. The test named “foreign or invalid” in read.rs itself only supplies urn:expired; broader coverage comes from cache tests.
Projection unavailable Recovery test proves failure preserves the old committed cursor/card and retry succeeds. It does not prove the claimed HTTP 503 behavior; R1-4 demonstrates the discovery gap.

No cross-user card read, URL-derived storage identity, adapter-scope widening, unauthenticated user-existence oracle, XXE fetch or internal-error disclosure found in the reviewed paths. Raw-path handling is fail-closed at the actual root mount; percent escapes, dot/empty segments, noncanonical IDs and extra item segments cannot select another user's book. Well-known redirects authenticate first and use credential-owned locations with no-store. Existing deferred D6 writes/CSRF, native Apple replay and perf-VM gates are not re-raised here.

Verification

On calternal-dev-01-compute02, isolated detached worktree ~/Developer/Worktrees/review794-round1 at the exact reviewed commit, Nix dev shell, through ~/bin/slot, -j4:

CARGO_TARGET_DIR=/home/claude/Developer/Worktrees/dav-transport/target ~/bin/slot cargo test -j4 -p calternal-dav --test reviewer_round1 --test reviewer_namespace --test transport --locked -- --nocapture

15 passed: all 11 existing transport integration tests and four temporary reviewer probes. Probe tests intentionally assert the observed buggy behavior; their passing reproduces the findings, not a fixed implementation. A 15,000-level XML nesting probe also completed without stack failure. Log: ~/review794-round1-verify.log; job exited 0. Temporary probes are only in the isolated VM worktree and were not committed/pushed. Also inspected the PR's recorded VM gate log, which ends EXIT=0; did not rerun its full clippy/CardDAV/auth/server train. Local read-only git diff --check origin/dev...HEAD passed. No control-host builds/tests/installs.

Required: fix R1-1 through R1-5 before landing. P0: none. P1: R1-1, authenticated XML allocation/response amplification can exhaust the shared server.

# Independent review, round 1 Reviewed **26936341e08e555369cc37702ff9eca239f11b03**. Read the common brief, ADRs 0064/0067/0068, this PR's plan/failure table, all five #782 review/status comments, and the transport, CardDAV adapter/projection/cache, authentication changes and server mount. Defensive review of our own code. No production code changed, committed or pushed. **Verdict: fix first. P0: 0; P1: 1; P2: 4; P3: 0.** One P2 is the still-open prerequisite from #782's final review, explicitly distinguished below. ## Findings ### R1-1 — P1: byte/node limits do not bound XML selection allocations or response expansion Location: [crates/dav/src/xml.rs:113](https://git.kayg.org/kayg/calternal/src/commit/26936341/crates/dav/src/xml.rs#L113), also lines 118, 207–215 and 264–280; principal call sites `crates/dav/src/serve.rs:530` and `:589`. A valid app password can submit a PROPFIND with one long inherited namespace and many `<x:a/>` properties. `selection` copies the entire resolved namespace into every property, then `respond` echoes that namespace declaration for every unknown property. Both happen on the async worker for principal requests. Neither the 1 MiB input cap, 20,000-node cap nor resource-count cap bounds the resulting allocation usefully. Small VM reproduction against the real router: an **11,336-byte** request, 512 properties, an 8,196-byte namespace and Depth 0 produced **4,196,352 bytes of owned namespace strings** and a **4,207,909-byte 207 response**. No stored contacts are needed. Scaling the same accepted shape to a 512 KiB namespace and 19,000 property nodes stays below both input limits while demanding roughly **9.3 GiB just for namespace copies**, before response strings. This is an allocation-derived exhaustion scenario; I deliberately did not run an OOM-sized request. Repeated `address-data` properties can additionally multiply returned card bytes, beyond the PR's stated one-copy-per-card 250 MiB estimate. **Fix:** enforce namespace/name length and aggregate selection-allocation budgets before cloning; deduplicate requested qualified properties; impose a checked serialized-byte budget covering every property/resource, including unknown-property echoes and repeated body properties. Return a bounded protocol error on overflow. Keep principal parsing/serialization off async workers as well. Add the inherited-long-namespace and duplicate-address-data attacks below the byte/node caps; assert bounded allocation/output and prompt rejection. ### R1-2 — P2: #782's final required cold-auth admission fix is absent at the mounted head Location: [crates/auth/src/app_password.rs:264](https://git.kayg.org/kayg/calternal/src/commit/26936341/crates/auth/src/app_password.rs#L264), `crates/auth/src/lib.rs:133`, and the new mount at `crates/calternal/src/app.rs:87`. This is the existing [#782 R3-1 prerequisite](https://git.kayg.org/kayg/calternal/pulls/782#issuecomment-70797), not a newly discovered auth issue or a fourth auth review. The final review explicitly requires it before mounting. Head 26936341 still has only the eight worker permits and `timeout(COLD_WAIT, acquire_owned())`; there is no separate pending-admission cap. Arbitrarily many fresh plausible junk credentials can join the 250 ms queue. Source exhaustion changes the post-lookup result rather than admission. This PR exposes that code publicly while claiming the final review fixes were incorporated. **Fix:** carry the required bounded pending-cold admission budget into the landing dependency/head; acquire it with `try_acquire_owned` before the FIFO wait, retain it through the operation and preserve warm-cache bypass. Prove immediate excess rejection and permit release on timeout/cancellation with a blocked DB worker. Existing finite-burst auth tests do not prove a bound on retained pending requests. Retaining the prior P2 severity; no measured OOM claim. ### R1-3 — P2: the transport's new blocking budget also leaves an unbounded retained-request queue Location: [crates/dav/src/serve.rs:171](https://git.kayg.org/kayg/calternal/src/commit/26936341/crates/dav/src/serve.rs#L171), with body buffering/copying at lines 144 and 161 and readiness waits at 303–312. The 32 permits bound running closures, not requests waiting for a permit. Each authenticated request has already buffered up to 1 MiB and can also retain the `body.to_vec()` copy while waiting. A slow cache/read, or one user's serialized projection barrier, can retain an arbitrary number of these requests; the blocking-permit wait has no timeout either. Invalid XML/Depth is only parsed after readiness and blocking admission, so rejected work can occupy this queue too. This is separate from the anonymous auth queue in R1-2: a single valid contacts credential reaches it repeatedly. **Fix:** add a finite request-admission budget before buffering/readiness, with immediate bounded overload responses and cancellation-safe ownership. Keep the existing running-work permit inside the blocking closure. Validate cheap protocol constraints before store initialization where possible. Test more concurrent requests than the admission cap against a held store/barrier, and verify immediate rejection, bounded retained admissions and cancellation release. The current ticker test proves offloading, not admission bounds. ### R1-4 — P2: principal discovery silently treats a failed projection as an absent home Location: [crates/dav/src/serve.rs:597](https://git.kayg.org/kayg/calternal/src/commit/26936341/crates/dav/src/serve.rs#L597). For authenticated Depth-1 principal discovery, `Ok(Some(h)) if h.ready().await.is_ok()` falls through to `Ok(_) => {}` when readiness fails. The reply is a successful 207 with the home silently omitted. The same failure on a direct home request correctly returns 503. An adapter `home()` error on discovery instead gets 500, also inconsistent with the direct-home 503 policy. A temporary broken projection therefore looks like a missing collection during discovery, contrary to the failure table's retryable-503 contract. VM fault injection confirmed **principal=207, one principal response only; direct home=503**. Neither response leaked the injected private error text. **Fix:** distinguish `None` from readiness failure; propagate home-open/readiness failures as a bounded 503 response consistently. Add HTTP tests for both failure points on principal Depth 1 and direct-home requests, followed by recovery. The cache recovery test only proves cursor/data retention and does not exercise these HTTP paths. ### R1-5 — P2: actual CardDAV namespaces collide with the CalDAV stub in shared discovery Location: [crates/dav/src/serve.rs:558](https://git.kayg.org/kayg/calternal/src/commit/26936341/crates/dav/src/serve.rs#L558), `crates/dav/src/xml.rs:222`, `crates/carddav/src/adapter.rs:20`, `crates/dav/tests/common/mod.rs:141` and `:318`. Principal namespace aggregation deduplicates whole `(prefix, URI)` pairs. Real CardDAV declares `c` for CardDAV, while the calendar stub declares `c` for CalDAV. With both mounted and a credential granting both scopes, discovery emits two different `xmlns:c` attributes on the same element, making the entire 207 response invalid XML. This is a shared-adapter composition bug; the current production mount has only CardDAV, so its single-adapter discovery does not trigger it. A VM probe using the exact real CardDAV namespace specification alongside the existing calendar stub produced **207 + `DuplicatedNamespace("c", …)`** from roxmltree. The existing two-adapter test misses this because `StubCards` declares `namespaces: &[]` instead of the real adapter's namespaces. **Fix:** give adapters globally distinct prefixes and validate conflicts when constructing the router, or scope each adapter's XML declarations locally. Add a coexistence test using the real CardDAV adapter and calendar stub, with dual-scope credentials and namespace-aware parsing of Depth 0/1 discovery. ## Failure-table test audit | Row | What the existing tests actually establish | |---|---| | Missing/wrong/revoked/wrong-scope credential | Real app-password transport checks prove denial and challenge; auth suite covers throttling. They do not establish bounded cold waiters: R1-2. Calendar-to-real-CardDAV item denial is not exercised by the contacts stub, though the production scope selection is correct by inspection. | | B URL / odd raw paths | Matrix proves refusal and no successful resource response for tested spellings. The gate and transport do not decode; production `home(user)` receives the credential owner. Tests do not count adapter calls, so the literal “no adapter lookup” claim is supported by code flow rather than a lookup assertion. | | Principal scope bypass | Single-scope Depth-1 tests prove ungranted home hrefs are absent; per-mount checks preserve adapter scope. Dual-scope tests use an unrealistically empty contacts namespace specification and miss R1-5. | | Oversized XML / DTD / excess hrefs | Correct-root internal-DTD, body-byte, node and 1,001-href tests prove their rejection paths. They miss legal-shape allocation/serialization amplification: R1-1. The external-entity test uses REPORT with a PROPFIND root, so its broad client-error assertion would also pass for the wrong report root. Explicit `allow_dtd: false` is the actual XXE protection. | | Too many members/changes | Stub tests count 1,000 resource results plus the documented extra marker and verify a continuation page. PROPFIND's collection counts toward 1,000; real metadata SQL has LIMIT. These tests establish count limits, not serialized-byte or retained-request bounds. | | Stale If-Match | GET/HEAD tests prove 412, weak If-Match rejection and 304 behavior; helper tests cover create/update preconditions and repeated headers. No writes ship, so these do not claim mutation-lock correctness. Missing GET targets return 404 before preconditions, consistent with ordinary request-error precedence. | | SQLite/cache stall | Current-thread ticker and projection-thread assertions prove session reads and refresh leave the async worker available. They do not exercise 32 concurrent jobs, cancellation ownership or overload admission: R1-3. The closure does retain its active permit through completion by inspection. | | Invalid/foreign/expired sync token | Transport proves malformed-token mapping; real cache tests prove foreign-user and old-generation rejection and stable continuation through changes. The test named “foreign or invalid” in `read.rs` itself only supplies `urn:expired`; broader coverage comes from cache tests. | | Projection unavailable | Recovery test proves failure preserves the old committed cursor/card and retry succeeds. It does not prove the claimed HTTP 503 behavior; R1-4 demonstrates the discovery gap. | No cross-user card read, URL-derived storage identity, adapter-scope widening, unauthenticated user-existence oracle, XXE fetch or internal-error disclosure found in the reviewed paths. Raw-path handling is fail-closed at the actual root mount; percent escapes, dot/empty segments, noncanonical IDs and extra item segments cannot select another user's book. Well-known redirects authenticate first and use credential-owned locations with no-store. Existing deferred D6 writes/CSRF, native Apple replay and perf-VM gates are not re-raised here. ## Verification On **calternal-dev-01-compute02**, isolated detached worktree `~/Developer/Worktrees/review794-round1` at the exact reviewed commit, Nix dev shell, through `~/bin/slot`, `-j4`: ```sh CARGO_TARGET_DIR=/home/claude/Developer/Worktrees/dav-transport/target ~/bin/slot cargo test -j4 -p calternal-dav --test reviewer_round1 --test reviewer_namespace --test transport --locked -- --nocapture ``` **15 passed:** all 11 existing transport integration tests and four temporary reviewer probes. Probe tests intentionally assert the observed buggy behavior; their passing reproduces the findings, not a fixed implementation. A 15,000-level XML nesting probe also completed without stack failure. Log: `~/review794-round1-verify.log`; job exited 0. Temporary probes are only in the isolated VM worktree and were not committed/pushed. Also inspected the PR's recorded VM gate log, which ends EXIT=0; did not rerun its full clippy/CardDAV/auth/server train. Local read-only `git diff --check origin/dev...HEAD` passed. No control-host builds/tests/installs. **Required: fix R1-1 through R1-5 before landing. P0: none. P1: R1-1, authenticated XML allocation/response amplification can exhaust the shared server.**
Author
Owner

Round 1 implementation response

Pushed 9a6c56a86f to the same branch. Re-merged the finalized #782 head a59f782e first. All five findings fixed; no finding waived. Never merged. This is the implementation response to review round 1, not another independent review round.

Finding Status Change and regression evidence
R1-1 P1: XML/selection/response allocation amplification Fixed Pre-parse 1 MiB body cap; allocation-free lexical preflight (depth/elements/tag bound, no DTD), limited namespace-aware tree, name and aggregate owned-selection budgets, deduplication of qualified properties, checked 8 MiB serialized replies, bounded sync payload pages, and principal work off async workers. review_round1, real CardDAV repeated-address-data/aggregate-report tests and memory_bound fail before and pass after.
R1-2 P2: missing #782 cold admission Fixed by dependency merge Carries a59f782e pending try-acquire before FIFO, warm-cache bypass, timeout/cancellation release. Adapted its test to this branch's scope-list API. The blocked-DB regression passes; in the isolated inverse-patch fixture, removing only pending acquisition makes it fail.
R1-3 P2: unbounded retained DAV waiters Fixed 64 router / 4 owner admissions before buffering/readiness, immediate bounded 429 + Retry-After. Leases survive active blocking cancellation and unread response bodies. Held-store overload, oversized-body overload, cancellation/recovery, global-budget and slow-reader regressions pass. The held-store test queued before the fix; the slow-reader test returned 200 instead of 429 before body ownership was added.
R1-4 P2: discovery hides failed projections Fixed Both home-open and readiness failures return 503 consistently for principal Depth 1 and direct-home requests; None remains absence. HTTP regression covers both fault points, private-error suppression and recovery. Original probe returned principal 207/direct 503.
R1-5 P2: CardDAV/CalDAV prefix collision Fixed Distinct c/cal bindings plus construction-time conflict validation. Real CardDAV + calendar adapter test parses Depth 0/1 discovery and checks namespace-aware properties. On original 26936341 it fails with DuplicatedNamespace("c", …).

The reviewer's original probes were recovered from review794-round1 on dev-01. Failing-before logs: ~/dav794-r1-red.log, ~/dav794-r1-red-bounds.log, ~/dav794-r1-before.log; auth inverse-patch: ~/dav794-r1-auth-red.log; response-body admission: ~/dav794-r1-body-red.log. The original-head authenticated HTTP memory probe grew peak RSS by 131,068 KiB; the final test asserts both an absolute 64 MiB peak and less than 8 MiB growth. Final measurement: 12,884 KiB peak, 88 KiB growth, output under 1 KiB, 400 rejection. No OOM-sized input was needed.

Final authoritative gates on calternal-dev-01-compute02, Nix shell, ~/bin/slot, isolated target /home/claude/Developer/Worktrees/dav794-r1/target:

cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast
cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked
cargo test -j4 -p calternal --bin calternal contract --locked
cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings
cargo fmt --all -- --check
cargo test -j4 -p calternal-dav --test memory_bound --locked -- --nocapture

254 package tests passed, 7 existing ignores; mount and contract each passed; clippy/fmt clean; memory probe passed. ~/dav794-r1-verified.log ends GATES_EXIT=0. All 1,196 source files match the tested VM tree by SHA-256 (excluding VM-only Nix files). An earlier shared-target binary-reuse sample was discarded; these final results come from the isolated target.

Decisions: refuse serialized-byte overflow with bounded 507 and no partial success/token; byte-bounded raw sync pages use valid continuation tokens. Admission lasts through response-body drop. No production access, no Nix files committed, no merge. Inherited cold rebuild staging/cache compaction, Apple-native replay and perf-VM latency remain outside this review-fix pass. New-head independent review remains pending, within the three-round maximum.

## Round 1 implementation response Pushed **9a6c56a86f4123e24d7c9d451b7d332514030ed2** to the same branch. Re-merged the finalized #782 head **a59f782e** first. **All five findings fixed; no finding waived. Never merged.** This is the implementation response to review round 1, not another independent review round. | Finding | Status | Change and regression evidence | |---|---|---| | R1-1 P1: XML/selection/response allocation amplification | Fixed | Pre-parse 1 MiB body cap; allocation-free lexical preflight (depth/elements/tag bound, no DTD), limited namespace-aware tree, name and aggregate owned-selection budgets, deduplication of qualified properties, checked 8 MiB serialized replies, bounded sync payload pages, and principal work off async workers. `review_round1`, real CardDAV repeated-address-data/aggregate-report tests and `memory_bound` fail before and pass after. | | R1-2 P2: missing #782 cold admission | Fixed by dependency merge | Carries a59f782e pending try-acquire before FIFO, warm-cache bypass, timeout/cancellation release. Adapted its test to this branch's scope-list API. The blocked-DB regression passes; in the isolated inverse-patch fixture, removing only pending acquisition makes it fail. | | R1-3 P2: unbounded retained DAV waiters | Fixed | 64 router / 4 owner admissions before buffering/readiness, immediate bounded 429 + Retry-After. Leases survive active blocking cancellation and unread response bodies. Held-store overload, oversized-body overload, cancellation/recovery, global-budget and slow-reader regressions pass. The held-store test queued before the fix; the slow-reader test returned 200 instead of 429 before body ownership was added. | | R1-4 P2: discovery hides failed projections | Fixed | Both home-open and readiness failures return 503 consistently for principal Depth 1 and direct-home requests; None remains absence. HTTP regression covers both fault points, private-error suppression and recovery. Original probe returned principal 207/direct 503. | | R1-5 P2: CardDAV/CalDAV prefix collision | Fixed | Distinct `c`/`cal` bindings plus construction-time conflict validation. Real CardDAV + calendar adapter test parses Depth 0/1 discovery and checks namespace-aware properties. On original 26936341 it fails with DuplicatedNamespace("c", …). | The reviewer's original probes were recovered from `review794-round1` on dev-01. Failing-before logs: `~/dav794-r1-red.log`, `~/dav794-r1-red-bounds.log`, `~/dav794-r1-before.log`; auth inverse-patch: `~/dav794-r1-auth-red.log`; response-body admission: `~/dav794-r1-body-red.log`. The original-head authenticated HTTP memory probe grew peak RSS by 131,068 KiB; the final test asserts both an absolute 64 MiB peak and less than 8 MiB growth. Final measurement: **12,884 KiB peak, 88 KiB growth**, output under 1 KiB, 400 rejection. No OOM-sized input was needed. Final authoritative gates on **calternal-dev-01-compute02**, Nix shell, `~/bin/slot`, isolated target `/home/claude/Developer/Worktrees/dav794-r1/target`: ```sh cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked cargo test -j4 -p calternal --bin calternal contract --locked cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings cargo fmt --all -- --check cargo test -j4 -p calternal-dav --test memory_bound --locked -- --nocapture ``` **254 package tests passed, 7 existing ignores; mount and contract each passed; clippy/fmt clean; memory probe passed.** `~/dav794-r1-verified.log` ends `GATES_EXIT=0`. All 1,196 source files match the tested VM tree by SHA-256 (excluding VM-only Nix files). An earlier shared-target binary-reuse sample was discarded; these final results come from the isolated target. Decisions: refuse serialized-byte overflow with bounded 507 and no partial success/token; byte-bounded raw sync pages use valid continuation tokens. Admission lasts through response-body drop. No production access, no Nix files committed, no merge. Inherited cold rebuild staging/cache compaction, Apple-native replay and perf-VM latency remain outside this review-fix pass. New-head independent review remains pending, within the three-round maximum.
Author
Owner

Independent review, round 2

Reviewed 9a6c56a86f, including the finalized #782 dependency at a59f782e. Defensive review of our own code. Read the common brief, context/taste and relevant ADRs, then concentrated on the fix diff and targeted transport/auth/CardDAV files and tests. No production code changed or pushed; no merge or production access. Temporary probes live only in the isolated dev-01 review worktree.

Verdict: fix first. New findings: P0 0, P1 1, P2 1, P3 0. The response-admission finding below is a newly demonstrated hole in the round-1 fix, and means R1-3 is not fully fixed.

Round-1 finding Status at this head Does its regression test prove the fix?
R1-1 P1: XML selection/response amplification Verified for the original amplification paths Preflight bounds work before DOM construction; selection text is charged before copying; qualified properties are deduplicated; serialized output is checked. Namespace/deep-XML, repeated real address-data, checked-property output and aggregate-report regressions pass. The isolated memory child reads Linux VmHWM, including freed allocations, so it measures the right peak rather than only live memory after response completion. My rerun: 12,908 KiB peak, 112 KiB growth, 68,680-byte input, bounded 400. This input exceeds the 4 KiB lexical-tag bound and exercises early rejection; it does not establish memory bounds for accepted XML, concurrent requests or socket-retained replies. The latter fails below.
R1-2 P2: missing prerequisite cold-auth admission Verified a59f782e is incorporated. try_acquire_owned precedes FIFO admission, and warm-cache bypass remains. The blocked-DB test checks immediate excess rejection on the first poll, cancellation return, FIFO-timeout return and warm success. Targeted rerun passed. This establishes retained cold-request admission; it does not claim that cancellation aborts already-enqueued DB jobs (the separate DB channel is bounded).
R1-3 P2: unbounded retained DAV requests Not fixed in full The 64/router and 4/owner caps correctly precede buffering/readiness; overload, cancellation and blocking-closure ownership tests pass. However, unread_response_bodies_keep_their_user_admission holds unpolled oneshot responses. It never exercises Hyper consuming the frame and retaining its bytes under TCP backpressure. Real TCP clients bypass the response-retention mechanism: R2-1. There is also no upload/readiness deadline: R2-2.
R1-4 P2: principal discovery hides failed projection Verified Explicit home-open/readiness error branches return 503. HTTP regression tests both fault points for principal Depth 1 and direct home, suppress private text and then recover to 207. Rerun passed. None remains absence.
R1-5 P2: CardDAV/CalDAV prefix collision Verified Calendar uses cal; real CardDAV uses c; router construction rejects conflicting bindings, including the reserved DAV binding. The test in CardDAV read.rs, unlike the remaining CardLike stub test, mounts the real adapter with the calendar stub, parses Depth 0/1 XML namespace-aware and checks both home properties and response counts. Rerun passed.

New findings

R2-1 — P1: admission is released while Hyper still retains the large response frame

Location: crates/dav/src/serve.rs:191; inadequate regression at crates/dav/tests/admission.rs:282.

The lease is captured by the map_frame closure, not owned by the returned frame's bytes. These replies are a complete buffered byte frame. Hyper polls that frame, drops the exhausted body/closure and keeps the bytes in its connection write buffer while the client stops reading. The permit is available again even though the large allocation remains live. The new 8 MiB per-reply limit therefore does not bound aggregate outstanding response memory: one valid credential can repeat this on arbitrarily many connections, bypassing the shared mechanism intended to enforce the 4/owner and 64/router retention bounds.

Real-network reproduction: on dev-01, real CardDAV adapter and real app-password authentication through axum::serve, one 128 KiB contact, REPORT with 48 repeated hrefs, about 6 MiB per reply. Each client reads only the HTTP headers and keeps its TCP socket open without reading the body. All eight clients receive 207, and a ninth authenticated OPTIONS receives 204, rather than overload refusal. In the isolated single-probe run, RSS grows from 20,192 to 70,776 KiB. The test deliberately stops at eight sockets; no OOM-sized run. Existing unpolled-body admission tests still pass alongside this reproduction.

Fix: attach admission ownership to the actual byte allocation so it follows downstream frame clones/slices through the HTTP write buffer, for example an owner-backed Bytes allocation retaining the lease, or enforce an equivalent budget at the connection/write-completion layer. Merely capturing a permit in another body wrapper has the same lifetime hole. Add a real TCP backpressure regression: more than four nonreading responses for one owner must refuse further admissions while the bytes remain retained, then recover after clients disconnect. Verify the global cap too. A write deadline alone is insufficient to establish the promised concurrent-memory bound during that deadline.

R2-2 — P2: incomplete bodies can monopolize the new owner admission indefinitely

Location: crates/dav/src/serve.rs:159, body wait at serve.rs:167; home/readiness waits at serve.rs:362 and :370.

After taking admission, to_bytes(...).await has a byte limit but no time limit. Four authenticated requests with a valid Content-Length and an unfinished body can keep all four slots for that credential owner while doing no useful work. Every other installation belonging to that owner, across adapters, gets 429 until the attacker disconnects. Sixteen owners can occupy all 64 router slots. The same absence of a request deadline applies to async home/readiness waits. Normal serving has no compensating deadline: ShutdownIo's write grace starts only on shutdown, and its reads have no runtime idle deadline.

The TCP probe sends four PROPFIND headers with Content-Length 1024 and only one body byte: a fifth OPTIONS returns 429, still 429 after one second of inactivity, then 204 after the four clients disconnect. This finite probe establishes slot ownership/release; the indefinite duration follows from the unbounded await and absence of a runtime deadline, not from pretending a one-second wait proves infinity. Existing readiness tests release work by aborting it manually, so they do not prove autonomous recovery.

Fix: enforce bounded body idle and overall read deadlines and an async request/readiness deadline, returning a bounded retryable response and releasing the request lease. When fixing R2-1, also bound response-write lifetime so correctly retained response leases cannot be pinned forever. Keep active blocking work's lease until the work actually ends, even if its HTTP deadline expires. Add controlled-time/deadline tests showing that stalled input/readiness and a nonreading client recover capacity without client cooperation, while legitimate requests still complete.

Verification

All builds/tests ran on calternal-dev-01-compute02, detached worktree ~/Developer/Worktrees/review794-round2 at the reviewed commit, Nix shell, ~/bin/slot, -j4. No control-host builds/tests/installs. Used target directory /home/claude/Developer/Worktrees/dav794-r1/target; Cargo rebuilt the reviewed crates from the fresh review-worktree paths, as recorded in the logs, rather than relying on a previous test executable.

Inside nix develop -c bash -c, with that CARGO_TARGET_DIR:

~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round2 --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-dav --test admission --test memory_bound --test review_round1 --test review_namespace --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round2 --test read --test cache --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-auth cold_admission_is_bounded_and_cached_passwords_bypass_it --locked -- --nocapture

The 12 targeted DAV tests and 22 CardDAV read/cache tests passed. Both temporary TCP probes pass by asserting the observed problematic behavior; they reproduce findings, not fixes. The targeted auth admission test passed. Logs: ~/review794-r2.log and ~/review794-r2-extra.log. Local read-only git diff --check 26936341..9a6c56a8 passed. Did not rerun the full application/clippy train.

Required before landing: fix R2-1 and R2-2. This is review round 2 of the maximum 3; no additional rounds were started. No new cross-user read, scope widening or XML entity expansion found in the targeted changes.

# Independent review, round 2 Reviewed **9a6c56a86f4123e24d7c9d451b7d332514030ed2**, including the finalized #782 dependency at **a59f782e**. Defensive review of our own code. Read the common brief, context/taste and relevant ADRs, then concentrated on the fix diff and targeted transport/auth/CardDAV files and tests. No production code changed or pushed; no merge or production access. Temporary probes live only in the isolated dev-01 review worktree. **Verdict: fix first. New findings: P0 0, P1 1, P2 1, P3 0.** The response-admission finding below is a newly demonstrated hole in the round-1 fix, and means R1-3 is not fully fixed. | Round-1 finding | Status at this head | Does its regression test prove the fix? | |---|---|---| | R1-1 P1: XML selection/response amplification | **Verified** for the original amplification paths | Preflight bounds work before DOM construction; selection text is charged before copying; qualified properties are deduplicated; serialized output is checked. Namespace/deep-XML, repeated real `address-data`, checked-property output and aggregate-report regressions pass. The isolated memory child reads Linux **VmHWM**, including freed allocations, so it measures the right peak rather than only live memory after response completion. My rerun: **12,908 KiB peak, 112 KiB growth**, 68,680-byte input, bounded 400. This input exceeds the 4 KiB lexical-tag bound and exercises early rejection; it does **not** establish memory bounds for accepted XML, concurrent requests or socket-retained replies. The latter fails below. | | R1-2 P2: missing prerequisite cold-auth admission | **Verified** | a59f782e is incorporated. `try_acquire_owned` precedes FIFO admission, and warm-cache bypass remains. The blocked-DB test checks immediate excess rejection on the first poll, cancellation return, FIFO-timeout return and warm success. Targeted rerun passed. This establishes retained cold-request admission; it does not claim that cancellation aborts already-enqueued DB jobs (the separate DB channel is bounded). | | R1-3 P2: unbounded retained DAV requests | **Not fixed in full** | The 64/router and 4/owner caps correctly precede buffering/readiness; overload, cancellation and blocking-closure ownership tests pass. However, `unread_response_bodies_keep_their_user_admission` holds unpolled `oneshot` responses. It never exercises Hyper consuming the frame and retaining its bytes under TCP backpressure. Real TCP clients bypass the response-retention mechanism: **R2-1**. There is also no upload/readiness deadline: **R2-2**. | | R1-4 P2: principal discovery hides failed projection | **Verified** | Explicit home-open/readiness error branches return 503. HTTP regression tests both fault points for principal Depth 1 and direct home, suppress private text and then recover to 207. Rerun passed. `None` remains absence. | | R1-5 P2: CardDAV/CalDAV prefix collision | **Verified** | Calendar uses `cal`; real CardDAV uses `c`; router construction rejects conflicting bindings, including the reserved DAV binding. The test in **CardDAV `read.rs`**, unlike the remaining CardLike stub test, mounts the real adapter with the calendar stub, parses Depth 0/1 XML namespace-aware and checks both home properties and response counts. Rerun passed. | ## New findings ### R2-1 — P1: admission is released while Hyper still retains the large response frame Location: [crates/dav/src/serve.rs:191](https://git.kayg.org/kayg/calternal/src/commit/9a6c56a8/crates/dav/src/serve.rs#L191); inadequate regression at [crates/dav/tests/admission.rs:282](https://git.kayg.org/kayg/calternal/src/commit/9a6c56a8/crates/dav/tests/admission.rs#L282). The lease is captured by the `map_frame` closure, not owned by the returned frame's bytes. These replies are a complete buffered byte frame. Hyper polls that frame, drops the exhausted body/closure and keeps the bytes in its connection write buffer while the client stops reading. The permit is available again even though the large allocation remains live. The new 8 MiB per-reply limit therefore does not bound aggregate outstanding response memory: one valid credential can repeat this on arbitrarily many connections, bypassing the shared mechanism intended to enforce the 4/owner and 64/router retention bounds. **Real-network reproduction:** on dev-01, real CardDAV adapter and real app-password authentication through `axum::serve`, one 128 KiB contact, REPORT with 48 repeated hrefs, about 6 MiB per reply. Each client reads only the HTTP headers and keeps its TCP socket open without reading the body. **All eight clients receive 207**, and a **ninth authenticated OPTIONS receives 204**, rather than overload refusal. In the isolated single-probe run, RSS grows from **20,192 to 70,776 KiB**. The test deliberately stops at eight sockets; no OOM-sized run. Existing unpolled-body admission tests still pass alongside this reproduction. **Fix:** attach admission ownership to the actual byte allocation so it follows downstream frame clones/slices through the HTTP write buffer, for example an owner-backed `Bytes` allocation retaining the lease, or enforce an equivalent budget at the connection/write-completion layer. Merely capturing a permit in another body wrapper has the same lifetime hole. Add a real TCP backpressure regression: more than four nonreading responses for one owner must refuse further admissions while the bytes remain retained, then recover after clients disconnect. Verify the global cap too. A write deadline alone is insufficient to establish the promised concurrent-memory bound during that deadline. ### R2-2 — P2: incomplete bodies can monopolize the new owner admission indefinitely Location: [crates/dav/src/serve.rs:159](https://git.kayg.org/kayg/calternal/src/commit/9a6c56a8/crates/dav/src/serve.rs#L159), body wait at [serve.rs:167](https://git.kayg.org/kayg/calternal/src/commit/9a6c56a8/crates/dav/src/serve.rs#L167); home/readiness waits at `serve.rs:362` and `:370`. After taking admission, `to_bytes(...).await` has a byte limit but no time limit. Four authenticated requests with a valid Content-Length and an unfinished body can keep all four slots for that credential owner while doing no useful work. Every other installation belonging to that owner, across adapters, gets 429 until the attacker disconnects. Sixteen owners can occupy all 64 router slots. The same absence of a request deadline applies to async home/readiness waits. Normal serving has no compensating deadline: `ShutdownIo`'s write grace starts only on shutdown, and its reads have no runtime idle deadline. The TCP probe sends four PROPFIND headers with Content-Length 1024 and only one body byte: a fifth OPTIONS returns **429**, still **429 after one second of inactivity**, then **204 after the four clients disconnect**. This finite probe establishes slot ownership/release; the indefinite duration follows from the unbounded await and absence of a runtime deadline, not from pretending a one-second wait proves infinity. Existing readiness tests release work by aborting it manually, so they do not prove autonomous recovery. **Fix:** enforce bounded body idle and overall read deadlines and an async request/readiness deadline, returning a bounded retryable response and releasing the request lease. When fixing R2-1, also bound response-write lifetime so correctly retained response leases cannot be pinned forever. Keep active blocking work's lease until the work actually ends, even if its HTTP deadline expires. Add controlled-time/deadline tests showing that stalled input/readiness and a nonreading client recover capacity without client cooperation, while legitimate requests still complete. ## Verification All builds/tests ran on **calternal-dev-01-compute02**, detached worktree `~/Developer/Worktrees/review794-round2` at the reviewed commit, Nix shell, `~/bin/slot`, `-j4`. No control-host builds/tests/installs. Used target directory `/home/claude/Developer/Worktrees/dav794-r1/target`; Cargo rebuilt the reviewed crates from the fresh review-worktree paths, as recorded in the logs, rather than relying on a previous test executable. Inside `nix develop -c bash -c`, with that `CARGO_TARGET_DIR`: ```sh ~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round2 --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-dav --test admission --test memory_bound --test review_round1 --test review_namespace --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round2 --test read --test cache --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-auth cold_admission_is_bounded_and_cached_passwords_bypass_it --locked -- --nocapture ``` The 12 targeted DAV tests and 22 CardDAV read/cache tests passed. Both temporary TCP probes pass by asserting the observed problematic behavior; they reproduce findings, not fixes. The targeted auth admission test passed. Logs: `~/review794-r2.log` and `~/review794-r2-extra.log`. Local read-only `git diff --check 26936341..9a6c56a8` passed. Did not rerun the full application/clippy train. **Required before landing:** fix R2-1 and R2-2. This is review round 2 of the maximum 3; no additional rounds were started. No new cross-user read, scope widening or XML entity expansion found in the targeted changes.
Author
Owner

Round-2 implementation plan: retain admission in owner-backed response Bytes, including downstream clones/slices. Reserve each admitted request’s maximum 8 MiB reply before work against a 64 MiB global response budget (8 reservations), while preserving 4/owner and 64/router ceilings. Use tower-http handler/body timeout layers, plus a DAV-aware listener to enforce write idle and whole-request deadlines through TCP write completion. Defaults: 60 s total, 10 s idle; configurable. Blocking closures retain admission until they actually end. O(body + reply) work, O(admitted requests + bounded bytes) memory.

failure effect detection recovery test
slow TCP reader retained bounded bytes and admission byte owner plus write idle/total deadline close connection, free bytes/permit eight real CardDAV TCP clients, memory and recovery
stalled upload/readiness bounded occupied slot body idle/handler total deadline close connection, retry stalled upload/readiness regression
global reply pressure no additional reply allocation pre-work global reservation immediate 429, retry after completion multiple-owner/global admission
cancelled blocking work running work stays charged lease inside blocking closure release when work ends existing blocking cancellation regression

Gates on dev-01: dav/carddav/auth/server packages, dav_is_mounted, contract, clippy -D warnings, fmt. Final review round 3 remains pending; never merge.

Round-2 implementation plan: retain admission in owner-backed response Bytes, including downstream clones/slices. Reserve each admitted request’s maximum 8 MiB reply before work against a 64 MiB global response budget (8 reservations), while preserving 4/owner and 64/router ceilings. Use tower-http handler/body timeout layers, plus a DAV-aware listener to enforce write idle and whole-request deadlines through TCP write completion. Defaults: 60 s total, 10 s idle; configurable. Blocking closures retain admission until they actually end. O(body + reply) work, O(admitted requests + bounded bytes) memory. | failure | effect | detection | recovery | test | |---|---|---|---|---| | slow TCP reader | retained bounded bytes and admission | byte owner plus write idle/total deadline | close connection, free bytes/permit | eight real CardDAV TCP clients, memory and recovery | | stalled upload/readiness | bounded occupied slot | body idle/handler total deadline | close connection, retry | stalled upload/readiness regression | | global reply pressure | no additional reply allocation | pre-work global reservation | immediate 429, retry after completion | multiple-owner/global admission | | cancelled blocking work | running work stays charged | lease inside blocking closure | release when work ends | existing blocking cancellation regression | Gates on dev-01: dav/carddav/auth/server packages, dav_is_mounted, contract, clippy -D warnings, fmt. Final review round 3 remains pending; never merge.
Author
Owner

Round 2 implementation status

Pushed 9e95ad41a0 to feat/dav-transport. Both required fixes are implemented and verified. FINAL independent review round 3 remains pending. Never merged.

Finding / gate Status Evidence
R2-1 P1: slow TCP readers bypass response admission Implemented; regressions pass Admission and byte reservation live in owner-backed response Bytes, including clones/slices. The DAV listener keeps Hyper in queued-write mode, including scalar-I/O fallback; the production shutdown wrapper forwards vectored writes. Eight real authenticated CardDAV TCP clients yield four 207 replies and four immediate 429 replies. The next same-owner OPTIONS is 429.
Global in-flight reply bytes Bounded before work Default 64 MiB reservation budget, 8 MiB reserved per admitted request, hence at most eight requests under that budget, additionally at most four per owner and 64 by count. The configured 16 MiB TCP regression admits only two large replies and refuses another owner before adapter work. Unit tests verify cross-owner reservation and clone/drop recovery.
R2-2 P2: stalled uploads/readiness hold slots indefinitely Implemented; regressions pass Configurable 60 s total and 10 s idle defaults. Tower-http handler and body-read timeouts cover authentication, upload and readiness/handling. The connection listener covers write idle and the same absolute request deadline through TCP writes. Expiry closes the connection. Real TCP tests cover stalled input, periodic upload progress, independent idle/total expiry, and recovery without client disconnect.
Blocking work after cancellation/timeout Charged until it ends Both cancellation and HTTP-timeout regressions retain admission and reply reservation while the blocking closure runs, then recover.
Normal clients Pass Each socket scenario finishes a normal ~6 MiB, 48-resource CardDAV reply after recovery; existing HTTP transport/read/auth/server suites remain green.
Package tests 264 passed, 0 failed, 7 existing ignores DAV 34, CardDAV 28, auth 179, server 23.
dav_is_mounted / contract / shutdown_io Pass One mount, one contract, two shutdown I/O tests.
Clippy -D warnings / fmt Pass All requested packages and targets; final source.
Source identity 1,198 files identical SHA-256 comparison between control and tested dev-01 tree, excluding VM-only Nix files. No Nix files committed.

Final authoritative dev-01 worktree and target: /home/claude/Developer/Worktrees/dav794-r2 and its own target/. All commands ran in nix develop -c bash -c through one ~/bin/slot, never on the control host:

cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast
cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked
cargo test -j4 -p calternal --bin calternal contract --locked
cargo test -j4 -p calternal --bin calternal shutdown_io --locked
cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings
cargo fmt --all -- --check
cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture

~/dav794-r2-final.log ends GATES_EXIT=0. The isolated first eight-reader case measured 47,324 KiB VmHWM, 26,328 KiB growth; assertions bound growth below 56 MiB and absolute peak below 96 MiB. The four socket scenarios cover total timeout, idle timeout, global byte pressure and client disconnect. Peak across their sequential allocations was 69,128 KiB. These are process-memory observations, not a claim about all server caches.

Failing-before evidence: ~/dav794-r2-red.log fails because client five receives 207 instead of 429 at 9a6c56a8. A further controlled-clock regression, ~/dav794-r2-anchor-red.log, proves that starting a relative timer only when its task is scheduled extends a 60 s deadline to 120 s; the final code captures an absolute deadline at request entry, and that test passes. An initial new-listener test also caught Hyper flattening when vectored writes were not advertised; queued-write preservation is part of the final design, rather than assuming a byte owner alone is sufficient.

Decisions and limits: reserve maximum reply size up front instead of adding dynamic allocation exceptions; defaults therefore allow eight concurrent requests, with four per owner. Configuration is calternal_dav::Config, shared with the listener. On HTTP/2, deadline expiry closes the shared connection, so other streams retry. Blocking work cannot be forcibly preempted and intentionally retains its charge until it ends. Native Apple replay and perf-VM latency gates remain the previously deferred work; no such results are claimed. No production access or merge. This is an implementation response to round 2, not an additional independent review round.

## Round 2 implementation status Pushed **9e95ad41a04b0d3d0bf3ffed84b0d67848b46954** to `feat/dav-transport`. Both required fixes are implemented and verified. **FINAL independent review round 3 remains pending. Never merged.** | Finding / gate | Status | Evidence | |---|---|---| | R2-1 P1: slow TCP readers bypass response admission | Implemented; regressions pass | Admission and byte reservation live in owner-backed response Bytes, including clones/slices. The DAV listener keeps Hyper in queued-write mode, including scalar-I/O fallback; the production shutdown wrapper forwards vectored writes. Eight real authenticated CardDAV TCP clients yield four 207 replies and four immediate 429 replies. The next same-owner OPTIONS is 429. | | Global in-flight reply bytes | Bounded before work | Default 64 MiB reservation budget, 8 MiB reserved per admitted request, hence at most eight requests under that budget, additionally at most four per owner and 64 by count. The configured 16 MiB TCP regression admits only two large replies and refuses another owner before adapter work. Unit tests verify cross-owner reservation and clone/drop recovery. | | R2-2 P2: stalled uploads/readiness hold slots indefinitely | Implemented; regressions pass | Configurable 60 s total and 10 s idle defaults. Tower-http handler and body-read timeouts cover authentication, upload and readiness/handling. The connection listener covers write idle and the same absolute request deadline through TCP writes. Expiry closes the connection. Real TCP tests cover stalled input, periodic upload progress, independent idle/total expiry, and recovery without client disconnect. | | Blocking work after cancellation/timeout | Charged until it ends | Both cancellation and HTTP-timeout regressions retain admission and reply reservation while the blocking closure runs, then recover. | | Normal clients | Pass | Each socket scenario finishes a normal ~6 MiB, 48-resource CardDAV reply after recovery; existing HTTP transport/read/auth/server suites remain green. | | Package tests | **264 passed, 0 failed, 7 existing ignores** | DAV 34, CardDAV 28, auth 179, server 23. | | `dav_is_mounted` / `contract` / `shutdown_io` | **Pass** | One mount, one contract, two shutdown I/O tests. | | Clippy `-D warnings` / fmt | **Pass** | All requested packages and targets; final source. | | Source identity | **1,198 files identical** | SHA-256 comparison between control and tested dev-01 tree, excluding VM-only Nix files. No Nix files committed. | Final authoritative dev-01 worktree and target: `/home/claude/Developer/Worktrees/dav794-r2` and its own `target/`. All commands ran in `nix develop -c bash -c` through one `~/bin/slot`, never on the control host: ```sh cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked cargo test -j4 -p calternal --bin calternal contract --locked cargo test -j4 -p calternal --bin calternal shutdown_io --locked cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings cargo fmt --all -- --check cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture ``` `~/dav794-r2-final.log` ends **GATES_EXIT=0**. The isolated first eight-reader case measured **47,324 KiB VmHWM, 26,328 KiB growth**; assertions bound growth below 56 MiB and absolute peak below 96 MiB. The four socket scenarios cover total timeout, idle timeout, global byte pressure and client disconnect. Peak across their sequential allocations was 69,128 KiB. These are process-memory observations, not a claim about all server caches. Failing-before evidence: `~/dav794-r2-red.log` fails because client five receives 207 instead of 429 at 9a6c56a8. A further controlled-clock regression, `~/dav794-r2-anchor-red.log`, proves that starting a relative timer only when its task is scheduled extends a 60 s deadline to 120 s; the final code captures an absolute deadline at request entry, and that test passes. An initial new-listener test also caught Hyper flattening when vectored writes were not advertised; queued-write preservation is part of the final design, rather than assuming a byte owner alone is sufficient. Decisions and limits: reserve maximum reply size up front instead of adding dynamic allocation exceptions; defaults therefore allow eight concurrent requests, with four per owner. Configuration is `calternal_dav::Config`, shared with the listener. On HTTP/2, deadline expiry closes the shared connection, so other streams retry. Blocking work cannot be forcibly preempted and intentionally retains its charge until it ends. Native Apple replay and perf-VM latency gates remain the previously deferred work; no such results are claimed. No production access or merge. This is an implementation response to round 2, not an additional independent review round.
Author
Owner

Independent review, round 3 (final)

Reviewed 9e95ad41a0, including rounds 1/2 and implementation status #issuecomment-71076. Defensive review of our own code. Verdict: small fix (P2-or-lower only). New findings: P0 0, P1 0, P2 1, P3 0. The memory/admission fixes hold in the targeted tests; the requested slow-phone compatibility check does not.

Item Status Independent evidence
R2-1: slow readers bypass response admission Verified fixed for the reproduced HTTP/1 TCP path Reran all four network scenarios. Eight same-owner slow readers receive four 207 and four 429 replies; further same-owner admission is refused while bytes remain held. Owner-backed Bytes retain the lease through clones/slices; listener advertises vectored writes and preserves the queued-buffer path, including scalar fallback.
Global response budget Verified reservation mechanism and targeted cross-owner pressure Admission reserves one 8 MiB unit before body/store work, from the default 64 MiB budget, alongside owner/count caps. The 16 MiB network scenario admits two replies and refuses another owner. This bounds admitted reply reservations, not every cache or transient allocation in the process.
Memory and autonomous response-slot recovery Verified First isolated eight-reader case: 47,256 KiB VmHWM, 26,248 KiB growth. Independent total-expiry, idle-expiry, global-budget and disconnect scenarios pass and recover admission; normal fast ~6 MiB replies subsequently complete.
R2-2: stalled uploads/readiness Verified fixed for retention/recovery All three real-TCP upload cases pass: body idle, total timeout and periodically progressing upload. Capacity returns without client disconnect. Readiness deadline regression also passes.
Blocking cancellation/timeout Verified Both regressions keep admission charged until blocking work actually ends. Downstream byte clone/slice and unread-body tests pass.
5,000-contact enumeration and fetch Verified with explicit protocol steps Real CardDAV/auth fixture: sync-collection returns five pages with 1,000 unique contacts each, then five bounded 1,000-href multigets return all 5,000 cards. Depth-1 PROPFIND returns the collection, 999 contacts and an overflow marker, without a continuation token. It is not independently pageable; this test deliberately uses advertised sync-collection to finish enumeration and does not establish native-client fallback behavior.
Large reply over a continuously progressing slow phone link Fails: R3-1 A valid 48-contact ~6 MiB multiget is cut off under the new deadline policy despite continuous reads. The existing “normal client” regression drains in under five seconds and misses this case.

New finding

R3-1 — P2: response deadlines truncate legitimate progressing syncs, without a resumable result

Locations: connection.rs:179, connection.rs:83, defaults at serve.rs:37.

Scenario: real authenticated CardDAV, 48 distinct valid ~128 KiB cards, one 6,308,392-byte multiget reply, comfortably below the 8 MiB per-reply bound. TCP receiver buffer is 16 KiB. The client reads up to 4 KiB every 125 ms, about 32 KiB/s, for 65 seconds, then drains quickly. There are over 500 successful reads; the client never deliberately pauses for ten seconds. Default 60 s total / 10 s idle returns incomplete XML.

Isolating the two deadlines on the same fixture:

Total / write idle Bytes received Complete XML
60 s / 180 s 4,824,913 No
180 s / 10 s 3,563,345 No
180 s / 180 s 6,308,392 Yes

The idle timer measures the interval without a successful application socket write, not absence of delivery to the client. TCP can remain non-writable while its existing send buffer drains continuously to a slow receiver, so this is not a reliable ten-second “client made no progress” detector. Independently, the absolute 60-second timer ends a valid transfer even when the idle window is relaxed. After the 207 headers have been sent, closure leaves truncated XML, not a smaller valid page or continuation. Retrying the same fixed client batch repeats the failure; the 64 MiB aggregate reservation does not help.

Fix: choose and document a supported minimum transfer rate and size the response lifetime and pending-write tolerance for a maximum-size reply at that rate, including TCP buffering. Keep upload/readiness deadlines separate if needed. Preserve the byte-owned reservations and bounded stalled-client recovery. A response-specific configuration with suitable defaults is a small change; do not merely increase the total timeout while leaving the demonstrated idle false positive. Alternatively arrange smaller valid replies before sending headers, with real protocol continuation where supported. Add the continuously reading TCP case alongside the nonreading-client regressions. The main application currently selects library defaults, so a test-only configuration change does not fix deployed behavior.

Severity is P2: this is a slow-link/batch-size-dependent read availability regression, with smaller-request or longer-deadline workarounds, rather than a new memory-bound escape or data-loss finding.

Verification and scope

All builds/tests ran on calternal-dev-01-compute02, detached worktree ~/Developer/Worktrees/review794-round3, exact reviewed HEAD. Used Nix, ~/bin/slot, -j4, and CARGO_TARGET_DIR=/home/claude/Developer/Worktrees/dav794-r2/target; logs show recompilation from the round-3 worktree. Tracked application sources are unchanged; only VM-only Nix files and an untracked review probe were added there.

Inside nix develop -c bash -c:

~/bin/slot cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-dav --test admission --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-dav --lib connection:: --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round3 reviewer_ --locked -- --nocapture
~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round3 reviewer_slow --locked -- --nocapture

Committed regressions: 2 network + 6 admission + 4 connection tests passed. Temporary 5,000-contact probe passed. The first slow probe failed its expectation that increasing only total time would permit completion, exposing the idle issue; the final three-configuration probe passes by asserting the observed truncation/completion outcomes above. These probe assertions reproduce the finding; they do not certify the deadline policy as correct. Logs: ~/review794-r3-existing.log, ~/review794-r3-connection.log, ~/review794-r3-probes.log, ~/review794-r3-slow-isolated.log. Control-host read-only git diff --check 9a6c56a8..9e95ad41 also passed. Full application/clippy train and native Apple behavior were not rerun or claimed.

No new P0/P1 found in this final round, so the mandatory “stop and simplify” trigger is not met. Address R3-1 before landing. No fourth independent review round started. No code pushed, merge, or production access.

# Independent review, round 3 (final) Reviewed **9e95ad41a04b0d3d0bf3ffed84b0d67848b46954**, including rounds 1/2 and implementation status #issuecomment-71076. Defensive review of our own code. **Verdict: small fix (P2-or-lower only). New findings: P0 0, P1 0, P2 1, P3 0.** The memory/admission fixes hold in the targeted tests; the requested slow-phone compatibility check does not. | Item | Status | Independent evidence | |---|---|---| | R2-1: slow readers bypass response admission | Verified fixed for the reproduced HTTP/1 TCP path | Reran all four network scenarios. Eight same-owner slow readers receive four 207 and four 429 replies; further same-owner admission is refused while bytes remain held. Owner-backed Bytes retain the lease through clones/slices; listener advertises vectored writes and preserves the queued-buffer path, including scalar fallback. | | Global response budget | Verified reservation mechanism and targeted cross-owner pressure | Admission reserves one 8 MiB unit before body/store work, from the default 64 MiB budget, alongside owner/count caps. The 16 MiB network scenario admits two replies and refuses another owner. This bounds admitted reply reservations, not every cache or transient allocation in the process. | | Memory and autonomous response-slot recovery | Verified | First isolated eight-reader case: **47,256 KiB VmHWM, 26,248 KiB growth**. Independent total-expiry, idle-expiry, global-budget and disconnect scenarios pass and recover admission; normal fast ~6 MiB replies subsequently complete. | | R2-2: stalled uploads/readiness | Verified fixed for retention/recovery | All three real-TCP upload cases pass: body idle, total timeout and periodically progressing upload. Capacity returns without client disconnect. Readiness deadline regression also passes. | | Blocking cancellation/timeout | Verified | Both regressions keep admission charged until blocking work actually ends. Downstream byte clone/slice and unread-body tests pass. | | 5,000-contact enumeration and fetch | Verified with explicit protocol steps | Real CardDAV/auth fixture: sync-collection returns five pages with 1,000 unique contacts each, then five bounded 1,000-href multigets return all 5,000 cards. Depth-1 PROPFIND returns the collection, 999 contacts and an overflow marker, **without a continuation token**. It is not independently pageable; this test deliberately uses advertised sync-collection to finish enumeration and does not establish native-client fallback behavior. | | Large reply over a continuously progressing slow phone link | **Fails: R3-1** | A valid 48-contact ~6 MiB multiget is cut off under the new deadline policy despite continuous reads. The existing “normal client” regression drains in under five seconds and misses this case. | ## New finding ### R3-1 — P2: response deadlines truncate legitimate progressing syncs, without a resumable result Locations: [connection.rs:179](https://git.kayg.org/kayg/calternal/src/commit/9e95ad41a04b0d3d0bf3ffed84b0d67848b46954/crates/dav/src/connection.rs#L179), [connection.rs:83](https://git.kayg.org/kayg/calternal/src/commit/9e95ad41a04b0d3d0bf3ffed84b0d67848b46954/crates/dav/src/connection.rs#L83), defaults at [serve.rs:37](https://git.kayg.org/kayg/calternal/src/commit/9e95ad41a04b0d3d0bf3ffed84b0d67848b46954/crates/dav/src/serve.rs#L37). **Scenario:** real authenticated CardDAV, 48 distinct valid ~128 KiB cards, one 6,308,392-byte multiget reply, comfortably below the 8 MiB per-reply bound. TCP receiver buffer is 16 KiB. The client reads up to 4 KiB every 125 ms, about 32 KiB/s, for 65 seconds, then drains quickly. There are over 500 successful reads; the client never deliberately pauses for ten seconds. Default 60 s total / 10 s idle returns incomplete XML. Isolating the two deadlines on the same fixture: | Total / write idle | Bytes received | Complete XML | |---|---:|---| | 60 s / 180 s | 4,824,913 | No | | 180 s / 10 s | 3,563,345 | No | | 180 s / 180 s | 6,308,392 | Yes | The idle timer measures the interval without a successful application socket write, not absence of delivery to the client. TCP can remain non-writable while its existing send buffer drains continuously to a slow receiver, so this is not a reliable ten-second “client made no progress” detector. Independently, the absolute 60-second timer ends a valid transfer even when the idle window is relaxed. After the 207 headers have been sent, closure leaves truncated XML, not a smaller valid page or continuation. Retrying the same fixed client batch repeats the failure; the 64 MiB aggregate reservation does not help. **Fix:** choose and document a supported minimum transfer rate and size the response lifetime and pending-write tolerance for a maximum-size reply at that rate, including TCP buffering. Keep upload/readiness deadlines separate if needed. Preserve the byte-owned reservations and bounded stalled-client recovery. A response-specific configuration with suitable defaults is a small change; do not merely increase the total timeout while leaving the demonstrated idle false positive. Alternatively arrange smaller valid replies before sending headers, with real protocol continuation where supported. Add the continuously reading TCP case alongside the nonreading-client regressions. The main application currently selects library defaults, so a test-only configuration change does not fix deployed behavior. Severity is P2: this is a slow-link/batch-size-dependent read availability regression, with smaller-request or longer-deadline workarounds, rather than a new memory-bound escape or data-loss finding. ## Verification and scope All builds/tests ran on **calternal-dev-01-compute02**, detached worktree `~/Developer/Worktrees/review794-round3`, exact reviewed HEAD. Used Nix, `~/bin/slot`, `-j4`, and `CARGO_TARGET_DIR=/home/claude/Developer/Worktrees/dav794-r2/target`; logs show recompilation from the round-3 worktree. Tracked application sources are unchanged; only VM-only Nix files and an untracked review probe were added there. Inside `nix develop -c bash -c`: ```sh ~/bin/slot cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-dav --test admission --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-dav --lib connection:: --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round3 reviewer_ --locked -- --nocapture ~/bin/slot cargo test -j4 -p calternal-carddav --test reviewer_round3 reviewer_slow --locked -- --nocapture ``` Committed regressions: **2 network + 6 admission + 4 connection tests passed**. Temporary 5,000-contact probe passed. The first slow probe failed its expectation that increasing only total time would permit completion, exposing the idle issue; the final three-configuration probe passes by asserting the observed truncation/completion outcomes above. These probe assertions reproduce the finding; they do not certify the deadline policy as correct. Logs: `~/review794-r3-existing.log`, `~/review794-r3-connection.log`, `~/review794-r3-probes.log`, `~/review794-r3-slow-isolated.log`. Control-host read-only `git diff --check 9a6c56a8..9e95ad41` also passed. Full application/clippy train and native Apple behavior were not rerun or claimed. No new P0/P1 found in this final round, so the mandatory “stop and simplify” trigger is not met. Address R3-1 before landing. No fourth independent review round started. No code pushed, merge, or production access.
Author
Owner

R3-1 implementation

Pushed 26edc6476b to feat/dav-transport. This implements the final review’s simpler response-specific size/minimum-rate rule. No fourth review round or merge.

Response transmission now gets min(response_timeout, idle_timeout + reply_bytes / min_response_rate), rounded up to milliseconds. Defaults are 2 KiB/s, 10 s slack, and a 30 min hard cap. The response timer replaces the preparation timer when the buffered reply is ready. Pending socket writes get the same buffer-aware tolerance: a TCP send buffer can keep draining to a reading client despite no successful application write for ten seconds. This fixes both cutoffs demonstrated in round 3, rather than only raising the total deadline.

Request-body no-progress timeout remains 10 s; authentication/upload/readiness/preparation remain limited to 60 s. Admission, owner-backed Bytes, 8 MiB reply reservations, 64 MiB global reservation budget and all existing memory/count limits are unchanged. Only the connection policy, config/response handoff, and network regressions changed.

Verification on dev-01, Nix shell through one ~/bin/slot, -j4:

  • 267 package tests passed, 0 failed, 7 existing ignores. Mount and contract each passed; clippy -D warnings and fmt passed.
  • Default-config real TCP client fetched 5,000 distinct contacts in five bounded 1,000-card multigets. First ~6 MiB page used a 16 KiB receiver buffer and 4 KiB reads every 125 ms for 65 seconds, then drained; all pages were complete, parseable XML. Baseline 9e95ad41 failed this regression with truncated XML after 514 reads over 65 s. Fixed run passed.
  • Below-rate real TCP reader was cut off with incomplete XML. This test raises the configurable rate to 2 MiB/s to exercise the size/rate budget in seconds; the hard cap remains 30 min, so it is not merely testing hard-cap expiry.
  • Existing eight-reader memory/admission test passed, with its response cap shortened for test duration: four 207, four 429, recovery, cross-owner budget and disconnect cases. First isolated case: 48,292 KiB peak, 27,512 KiB growth, within unchanged assertions. Controlled-clock test covers size-derived expiry and the 30 min hard cap; upload and blocking-retention regressions pass.

Exact final gates:

cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast
cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked
cargo test -j4 -p calternal --bin calternal contract --locked
cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings
cargo fmt --all -- --check

Additional focused checks: cargo test -j4 -p calternal-dav --lib connection:: --locked -- --nocapture and cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture.

Evidence: ~/dav794-r3-red.log and ~/dav794-r3-gates.log (ends GATES_EXIT=0). VM worktree: ~/Developer/Worktrees/dav794-r3-fix; reused target: /home/claude/Developer/Worktrees/dav794-r2/target, with recompilation from the new worktree recorded in the log. SHA-256 comparison confirms all 1,198 tracked files match the control worktree. VM-only Nix files were not committed.

Tradeoff: a stopped reader may hold its existing reservation for the size/rate response budget, up to 30 minutes. The hard cap takes precedence over the configured rate for large replies. This is the reviewer-suggested bounded response policy, with upload/preparation deadlines separate. No additional independent review or native-client claim.

## R3-1 implementation Pushed **26edc6476b4b25e1a337190021f2f8dde77ca61a** to `feat/dav-transport`. This implements the final review’s simpler response-specific size/minimum-rate rule. No fourth review round or merge. Response transmission now gets `min(response_timeout, idle_timeout + reply_bytes / min_response_rate)`, rounded up to milliseconds. Defaults are **2 KiB/s**, **10 s slack**, and a **30 min hard cap**. The response timer replaces the preparation timer when the buffered reply is ready. Pending socket writes get the same buffer-aware tolerance: a TCP send buffer can keep draining to a reading client despite no successful application write for ten seconds. This fixes both cutoffs demonstrated in round 3, rather than only raising the total deadline. Request-body no-progress timeout remains **10 s**; authentication/upload/readiness/preparation remain limited to **60 s**. Admission, owner-backed Bytes, 8 MiB reply reservations, 64 MiB global reservation budget and all existing memory/count limits are unchanged. Only the connection policy, config/response handoff, and network regressions changed. Verification on **dev-01**, Nix shell through one `~/bin/slot`, `-j4`: - **267 package tests passed, 0 failed, 7 existing ignores**. Mount and contract each passed; clippy `-D warnings` and fmt passed. - Default-config real TCP client fetched **5,000 distinct contacts in five bounded 1,000-card multigets**. First ~6 MiB page used a 16 KiB receiver buffer and 4 KiB reads every 125 ms for 65 seconds, then drained; all pages were complete, parseable XML. Baseline 9e95ad41 failed this regression with truncated XML after 514 reads over 65 s. Fixed run passed. - Below-rate real TCP reader was cut off with incomplete XML. This test raises the configurable rate to 2 MiB/s to exercise the size/rate budget in seconds; the hard cap remains 30 min, so it is not merely testing hard-cap expiry. - Existing eight-reader memory/admission test passed, with its response cap shortened for test duration: four 207, four 429, recovery, cross-owner budget and disconnect cases. First isolated case: **48,292 KiB peak, 27,512 KiB growth**, within unchanged assertions. Controlled-clock test covers size-derived expiry and the 30 min hard cap; upload and blocking-retention regressions pass. Exact final gates: ```sh cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked cargo test -j4 -p calternal --bin calternal contract --locked cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings cargo fmt --all -- --check ``` Additional focused checks: `cargo test -j4 -p calternal-dav --lib connection:: --locked -- --nocapture` and `cargo test -j4 -p calternal-carddav --test network_limits --locked -- --nocapture`. Evidence: `~/dav794-r3-red.log` and `~/dav794-r3-gates.log` (ends `GATES_EXIT=0`). VM worktree: `~/Developer/Worktrees/dav794-r3-fix`; reused target: `/home/claude/Developer/Worktrees/dav794-r2/target`, with recompilation from the new worktree recorded in the log. SHA-256 comparison confirms **all 1,198 tracked files match** the control worktree. VM-only Nix files were not committed. Tradeoff: a stopped reader may hold its existing reservation for the size/rate response budget, up to 30 minutes. The hard cap takes precedence over the configured rate for large replies. This is the reviewer-suggested bounded response policy, with upload/preparation deadlines separate. No additional independent review or native-client claim.
Author
Owner

stack refresh after #776/#777 round 1: updated #777 3c8ddf534f merged into feat/dav-transport, producing 80ec1fa39f. pushed only after all requested gates passed. never merged into dev. no additional independent review round launched.

This inherits bounded wire revision retention (default 1,024 changes, configurable Cache retention, expired tokens return RFC 6578 valid-sync-token) and a disk-staged rebuild from bounded 32-card/ID pages. Production code merged cleanly. Two append-only test conflicts were resolved by retaining every transport test and every new retention regression. Existing transport byte/resource limits and socket deadlines remain intact.

requested gate status evidence
dav/carddav/auth/server packages pass 0 failures, 7 existing ignores; includes TCP slow readers/deadlines, real journal projection and HTTP expired-token/full-resync
dav_is_mounted pass app::dav_tests::dav_is_mounted_with_real_scoped_auth_and_contact_projection
contract pass plugins::tests::contract_matches_the_server
clippy pass all requested packages/all targets, -D warnings
fmt pass whole workspace clean
rebuild memory on merged tree pass 50k cards 10,552 KiB peak RSS; two concurrent 1,000-card books with 224 KiB photos 32,544 KiB; both below 192 MiB assertion
crash/reopen and retention pass every staging batch boundary, old generation serving, lock exclusion, retry, snapshot floor/tombstones and bounded photo churn
source identity pass SHA-256 of all 445 Rust/Cargo files agrees between final control tree and tested VM, checked again after gates

VM: claude@calternal-dev-01-compute02, fresh isolated worktree and target /home/claude/Developer/Worktrees/dav794-retention-r1. All heavy commands in Nix through one ~/bin/slot, -j4. ~/dav794-retention-r1.log; ~/dav794-retention-r1.exit is 0.

cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast
cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked
cargo test -j4 -p calternal --bin calternal contract --locked
cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings
cargo fmt --all -- --check

Remaining limits are documented in #777's status: native Apple replay and perf-VM latency gates are not claimed; incremental journal replay still buffers its affected-card set, and per-book readiness can wait for a rebuild. Kill tests exercise staging batch boundaries, not power loss/deterministic interruption inside SQLite backup. feat/carddav-writes / #797 was deliberately left unchanged; its write-review implementation can incorporate this updated transport head in its own task. Independent review of the refreshed stack remains pending.

stack refresh after #776/#777 round 1: updated #777 **3c8ddf534f35851276f85cce93e3c29907618654** merged into `feat/dav-transport`, producing **80ec1fa39fe22af52f8ccddbd6bbe144a59d43eb**. pushed **only after all requested gates passed**. never merged into dev. no additional independent review round launched. This inherits bounded wire revision retention (default 1,024 changes, configurable Cache retention, expired tokens return RFC 6578 valid-sync-token) and a disk-staged rebuild from bounded 32-card/ID pages. Production code merged cleanly. Two append-only test conflicts were resolved by retaining every transport test and every new retention regression. Existing transport byte/resource limits and socket deadlines remain intact. | requested gate | status | evidence | |---|---|---| | dav/carddav/auth/server packages | pass | 0 failures, 7 existing ignores; includes TCP slow readers/deadlines, real journal projection and HTTP expired-token/full-resync | | dav_is_mounted | pass | `app::dav_tests::dav_is_mounted_with_real_scoped_auth_and_contact_projection` | | contract | pass | `plugins::tests::contract_matches_the_server` | | clippy | pass | all requested packages/all targets, -D warnings | | fmt | pass | whole workspace clean | | rebuild memory on merged tree | pass | 50k cards **10,552 KiB** peak RSS; two concurrent 1,000-card books with 224 KiB photos **32,544 KiB**; both below 192 MiB assertion | | crash/reopen and retention | pass | every staging batch boundary, old generation serving, lock exclusion, retry, snapshot floor/tombstones and bounded photo churn | | source identity | pass | SHA-256 of all **445 Rust/Cargo files** agrees between final control tree and tested VM, checked again after gates | VM: **claude@calternal-dev-01-compute02**, fresh isolated worktree and target `/home/claude/Developer/Worktrees/dav794-retention-r1`. All heavy commands in Nix through one `~/bin/slot`, `-j4`. `~/dav794-retention-r1.log`; `~/dav794-retention-r1.exit` is **0**. ```sh cargo test -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server --locked --no-fail-fast cargo test -j4 -p calternal --bin calternal dav_is_mounted --locked cargo test -j4 -p calternal --bin calternal contract --locked cargo clippy -j4 -p calternal-dav -p calternal-carddav -p calternal-auth -p calternal-server -p calternal --all-targets --locked -- -D warnings cargo fmt --all -- --check ``` Remaining limits are documented in [#777's status](https://git.kayg.org/kayg/calternal/pulls/777#issuecomment-71217): native Apple replay and perf-VM latency gates are not claimed; incremental journal replay still buffers its affected-card set, and per-book readiness can wait for a rebuild. Kill tests exercise staging batch boundaries, not power loss/deterministic interruption inside SQLite backup. `feat/carddav-writes` / #797 was deliberately left unchanged; its write-review implementation can incorporate this updated transport head in its own task. Independent review of the refreshed stack remains pending.
Author
Owner

Integration note after #776/#777 final review

Reviewed refreshed 21e678b258 only for regressions from re-merging #776/#777. This is the requested integration check, not a fourth transport review.

check status
Admission, owner/count/global response reservations unchanged
Memory/response caps and cache aggregate payload/index guard preserved
Request/response deadlines, minimum-rate policy and auth unchanged
Shared epoch fix preserved; same read guard and invalid-token mapping

verdict: ready within this integration scope. New findings: P0 0, P1 0, P2 0, P3 0. No new P0/P1; no stop-and-simplify trigger.

Diff 80ec1fa3..21e678b2 contains only five cache/regression files; DAV transport, adapter/properties, auth, server/mount, network tests and deadlines are byte-identical. Independent SHA-256 comparison confirms all 446 Rust/Cargo files in ~/Developer/Worktrees/dav794-epoch-r2 match 21e678b2 despite that VM worktree's older Git HEAD. Fresh #776/#777 targeted runs pass (21/25 tests), including both epoch interleavings and HTTP resync. Prior source-matched ~/carddav-epoch-final2.log has all three merged epoch regressions passing, broader transport/auth/socket gates and TRANSPORT_GREEN, exit 0. The attempted fresh #794 rerun remained queued and was cancelled before Cargo started; those prior runtime results are inspected evidence, not a fresh transport pass. Read-only diff/whitespace checks pass.

Existing native-client, perf-VM and backup-interruption limits remain. No code pushed or merge. See the final-round comments on #776/#777 for full evidence.

## Integration note after #776/#777 final review Reviewed refreshed **21e678b258f8fe7bb765897fc227d95e2b11abaf** only for regressions from re-merging #776/#777. This is the requested integration check, not a fourth transport review. | check | status | |---|---| | Admission, owner/count/global response reservations | unchanged | | Memory/response caps and cache aggregate payload/index guard | preserved | | Request/response deadlines, minimum-rate policy and auth | unchanged | | Shared epoch fix | preserved; same read guard and invalid-token mapping | **verdict: ready within this integration scope. New findings: P0 0, P1 0, P2 0, P3 0.** No new P0/P1; no stop-and-simplify trigger. Diff 80ec1fa3..21e678b2 contains only five cache/regression files; DAV transport, adapter/properties, auth, server/mount, network tests and deadlines are byte-identical. Independent SHA-256 comparison confirms all **446 Rust/Cargo files** in `~/Developer/Worktrees/dav794-epoch-r2` match 21e678b2 despite that VM worktree's older Git HEAD. Fresh #776/#777 targeted runs pass (21/25 tests), including both epoch interleavings and HTTP resync. Prior source-matched `~/carddav-epoch-final2.log` has all three merged epoch regressions passing, broader transport/auth/socket gates and `TRANSPORT_GREEN`, exit 0. The attempted fresh #794 rerun remained queued and was cancelled before Cargo started; those prior runtime results are inspected evidence, not a fresh transport pass. Read-only diff/whitespace checks pass. Existing native-client, perf-VM and backup-interruption limits remain. No code pushed or merge. See the final-round comments on #776/#777 for full evidence.
kayg changed target branch from dev to issue/carddav-read-perf-fix-r2 2026-11-07 20:32:08 +00:00
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

restacked only the transport feature on #777. kept dev strict DAV target handling and bounded scope cache; Notes scopes remain separate from the DAV gate. added verify_any for DAV any-scope checks, retained Notes scope separation, and matched current unavailable/503 retry outcome. removed obsolete single-scope helper and added current credentials to DAV transport fixtures.

head: 6458b50031c6bfc1fb52dcf1b480ab3f27506c27; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate passed. first-paint brotli vs dev: JS 230777 B (+0 B), CSS 18386 B (+0 B).

Rust gate results (Mac VM):

exit command
0 cargo fmt -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes -- --check
0 cargo check -j4 --locked -p calternal --bins
101 cargo check -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes --all-targets
101 cargo clippy -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes --all-targets -- -D warnings
0 cargo test -j4 --locked -p calternal-auth --lib
101 cargo test -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes
0 cargo check -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --all-targets
0 cargo clippy -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --all-targets -- -D warnings
0 cargo test -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --lib

server-wide all-target checks/tests are blocked on macOS by storage_startup.rs:147 borrowing Child immutably; clippy also reports unused state in storage.rs:265. both reproduce on dev. scoped auth/DAV/CardDAV/contact component checks, clippy and unit tests passed.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-794.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

housekeeping #1009: rebased+failing, left open for review. restacked only the transport feature on #777. kept dev strict DAV target handling and bounded scope cache; Notes scopes remain separate from the DAV gate. added verify_any for DAV any-scope checks, retained Notes scope separation, and matched current unavailable/503 retry outcome. removed obsolete single-scope helper and added current credentials to DAV transport fixtures. head: `6458b50031c6bfc1fb52dcf1b480ab3f27506c27`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate passed. first-paint brotli vs dev: JS 230777 B (+0 B), CSS 18386 B (+0 B). Rust gate results (Mac VM): | exit | command | |---|---| | 0 | `cargo fmt -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes -- --check` | | 0 | `cargo check -j4 --locked -p calternal --bins` | | 101 | `cargo check -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes --all-targets` | | 101 | `cargo clippy -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes --all-targets -- -D warnings` | | 0 | `cargo test -j4 --locked -p calternal-auth --lib` | | 101 | `cargo test -j4 --locked -p calternal-auth -p calternal -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts -p calternal-dav -p calternal-notes` | | 0 | `cargo check -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --all-targets` | | 0 | `cargo clippy -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --all-targets -- -D warnings` | | 0 | `cargo test -j4 --locked -p calternal-auth -p calternal-dav -p calternal-carddav -p calternal-contacts-actions -p calternal-contacts --lib` | server-wide all-target checks/tests are blocked on macOS by `storage_startup.rs:147` borrowing Child immutably; clippy also reports unused `state` in `storage.rs:265`. both reproduce on dev. scoped auth/DAV/CardDAV/contact component checks, clippy and unit tests passed. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/0aee78b2-28de-4cba-890a-830a5086e373). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-794.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
This pull request is broken due to missing fork information.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/dav-transport:feat/dav-transport
git switch feat/dav-transport

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.

git switch issue/carddav-read-perf-fix-r2
git merge --no-ff feat/dav-transport
git switch feat/dav-transport
git rebase issue/carddav-read-perf-fix-r2
git switch issue/carddav-read-perf-fix-r2
git merge --ff-only feat/dav-transport
git switch feat/dav-transport
git rebase issue/carddav-read-perf-fix-r2
git switch issue/carddav-read-perf-fix-r2
git merge --no-ff feat/dav-transport
git switch issue/carddav-read-perf-fix-r2
git merge --squash feat/dav-transport
git switch issue/carddav-read-perf-fix-r2
git merge --ff-only feat/dav-transport
git switch issue/carddav-read-perf-fix-r2
git merge feat/dav-transport
git push origin issue/carddav-read-perf-fix-r2
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!794
No description provided.