PERF: instant everywhere — app-wide speed architecture rules, audit and shared primitives #663

Open
opened 2026-10-02 04:40:28 +00:00 by kayg · 48 comments
Owner

Owner goal (2026-10-02)

"I want our app to be as fast and instantaneous as [a reference mail app the owner uses]. I want that translated to every interaction!"

What makes the reference feel instant (analysis of its architecture; we copy techniques only, never code)

It keeps no mail replica in the browser, and its server is a normal remote server. The speed comes from discipline, not locality:

  1. No external calls or file parsing on the interactive path. Every UI request is served from an indexed SQLite mirror (WAL, covering indexes). Provider writes go through a durable queue.
  2. Work is precomputed at ingest: snippets/previews, search text and sort keys are stored as columns.
  3. List rows carry enough to draw the detail header; only the body is fetched lazily.
  4. Revision-keyed client cache + strong ETags. A cached open makes zero requests and publishes no new model (~20–30 ms). The body cache is bounded by bytes.
  5. A bounded client window: first page ~100 rows plus one prefetch, then on demand. Caps on resident rows/bytes with LRU eviction. Server keyset pagination with signed cursors; never OFFSET, never whole-corpus responses.
  6. Optimistic mutations in one synchronous commit (hide/advance/select in the same frame). They carry client IDs, the server keeps durable receipts, and Undo replays the receipt. Rollback happens only on a definite rejection.
  7. Virtualised lists with narrow memoised rows. The highlight is an index, so moving it re-renders two rows. Keyboard handlers never await.
  8. Instant return to recent views: the last few view snapshots (rows + cursors + scroll) are kept and shown first, then one bounded catch-up runs.
  9. Push says only "changed since N". The client pulls a capped delta, coalesced (~100 ms), and unchanged objects keep their identity.
  10. Background work yields to the user: counts load after first paint; the server yields between batches and uses read-only workers. Latency budgets are enforced and measured (cached open ≤ 100 ms, action ≤ 150 ms, first usable view ≤ 1.5 s at 10k items), with content-free timing logs that split "accepted" from "durable".
    Extra levers it does not use and we can add: a persistent per-User cache of recent bodies/snapshots (userStorage/IndexedDB), ±1 neighbour prefetch (the blaze test #641), content-visibility, a service worker for the app shell, and route-level code splitting.

Rules for every calternal interaction (to add to DESIGN as a performance section and enforce in reviews)

  1. Never call an external system or read/parse Markdown files while handling a UI request. Serve from the SQLite index and precomputed columns; the indexer does the parsing.
  2. Every list response is bounded (≤ 100 rows and a byte cap) with keyset cursors.
  3. List rows contain everything the detail header needs. Bodies are lazy, cached by revision, and validated with strong ETags (304).
  4. Mutations update the UI synchronously in the same frame, with a client ID, server idempotency, a visible error only on definite rejection, and Undo as a durable inverse.
  5. Push is "changed since N" on one per-User stream. Clients pull capped deltas and keep identity for unchanged objects.
  6. Returning to any view shows its retained snapshot first. Never show a spinner or blank over data the User has already seen.
  7. Long lists are virtualised with narrow rows; the highlight is an index; keyboard handlers never await.
  8. Counts, stats and indexing run after first paint or off-thread and yield between batches. The indexer never blocks interactive reads (separate reader pool).
  9. Budgets, measured on the perf VM with HDD emulation and production builds (≥ 5 samples, median/p95/max): open from cache ≤ 100 ms; action ≤ 150 ms; tab switch warm ≤ 100 ms; first usable view ≤ 1.5 s (10k items); blaze test (#641) 0 incomplete frames.

Plan

  1. Audit (this issue): a matrix of every mode (Calendar, Notes, Files, Photos, Mail, Money, Settings, Search, Admin) × rules 1–9. For each cell: pass/fail with evidence (endpoint, file:line, a measured number). File one sub-issue per failing rule per mode, grouped so one job owns a shared primitive (one owner for shared fixes).
  2. Shared primitives first: (a) a revision-keyed client cache + ETag/304 on all read endpoints; (b) a view-snapshot LRU; (c) an optimistic mutation helper with client IDs and Undo receipts; (d) one per-User "changed since N" stream with a delta endpoint. Reuse what exists: userStorage #555, the #549 route cache, the Files change feed, toasts with Undo.
  3. Modes adopt the primitives; the blaze and tab-switch benchmarks prove each one.
## Owner goal (2026-10-02) "I want our app to be as fast and instantaneous as [a reference mail app the owner uses]. I want that translated to every interaction!" ## What makes the reference feel instant (analysis of its architecture; we copy techniques only, never code) It keeps **no** mail replica in the browser, and its server is a normal remote server. The speed comes from discipline, not locality: 1. **No external calls or file parsing on the interactive path.** Every UI request is served from an indexed SQLite mirror (WAL, covering indexes). Provider writes go through a durable queue. 2. **Work is precomputed at ingest:** snippets/previews, search text and sort keys are stored as columns. 3. **List rows carry enough to draw the detail header**; only the body is fetched lazily. 4. **Revision-keyed client cache + strong ETags.** A cached open makes zero requests and publishes no new model (~20–30 ms). The body cache is bounded by bytes. 5. **A bounded client window:** first page ~100 rows plus one prefetch, then on demand. Caps on resident rows/bytes with LRU eviction. Server keyset pagination with signed cursors; never OFFSET, never whole-corpus responses. 6. **Optimistic mutations in one synchronous commit** (hide/advance/select in the same frame). They carry client IDs, the server keeps durable receipts, and Undo replays the receipt. Rollback happens only on a definite rejection. 7. **Virtualised lists with narrow memoised rows.** The highlight is an index, so moving it re-renders two rows. Keyboard handlers never await. 8. **Instant return to recent views:** the last few view snapshots (rows + cursors + scroll) are kept and shown first, then one bounded catch-up runs. 9. **Push says only "changed since N".** The client pulls a capped delta, coalesced (~100 ms), and unchanged objects keep their identity. 10. **Background work yields to the user:** counts load after first paint; the server yields between batches and uses read-only workers. **Latency budgets** are enforced and measured (cached open ≤ 100 ms, action ≤ 150 ms, first usable view ≤ 1.5 s at 10k items), with content-free timing logs that split "accepted" from "durable". Extra levers it does not use and we can add: a persistent per-User cache of recent bodies/snapshots (userStorage/IndexedDB), ±1 neighbour prefetch (the blaze test #641), `content-visibility`, a service worker for the app shell, and route-level code splitting. ## Rules for every calternal interaction (to add to DESIGN as a performance section and enforce in reviews) 1. Never call an external system or read/parse Markdown files while handling a UI request. Serve from the SQLite index and precomputed columns; the indexer does the parsing. 2. Every list response is bounded (≤ 100 rows and a byte cap) with keyset cursors. 3. List rows contain everything the detail header needs. Bodies are lazy, cached by revision, and validated with strong ETags (304). 4. Mutations update the UI synchronously in the same frame, with a client ID, server idempotency, a visible error only on definite rejection, and Undo as a durable inverse. 5. Push is "changed since N" on one per-User stream. Clients pull capped deltas and keep identity for unchanged objects. 6. Returning to any view shows its retained snapshot first. Never show a spinner or blank over data the User has already seen. 7. Long lists are virtualised with narrow rows; the highlight is an index; keyboard handlers never await. 8. Counts, stats and indexing run after first paint or off-thread and yield between batches. The indexer never blocks interactive reads (separate reader pool). 9. Budgets, measured on the perf VM with HDD emulation and production builds (≥ 5 samples, median/p95/max): open from cache ≤ 100 ms; action ≤ 150 ms; tab switch warm ≤ 100 ms; first usable view ≤ 1.5 s (10k items); blaze test (#641) 0 incomplete frames. ## Plan 1. **Audit (this issue):** a matrix of every mode (Calendar, Notes, Files, Photos, Mail, Money, Settings, Search, Admin) × rules 1–9. For each cell: pass/fail with evidence (endpoint, file:line, a measured number). File one sub-issue per failing rule per mode, grouped so one job owns a shared primitive (one owner for shared fixes). 2. **Shared primitives first:** (a) a revision-keyed client cache + ETag/304 on all read endpoints; (b) a view-snapshot LRU; (c) an optimistic mutation helper with client IDs and Undo receipts; (d) one per-User "changed since N" stream with a delta endpoint. Reuse what exists: userStorage #555, the #549 route cache, the Files change feed, toasts with Undo. 3. Modes adopt the primitives; the blaze and tab-switch benchmarks prove each one.
Author
Owner

Audit started on job/instant-663, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Scope: documentation rules, mode × rule evidence matrix, locked perf-VM production/HDD measurements, shared-owner and adoption issues. No product code, push, deploy or product API changes. Existing #555, #549, #641, #642 and #640 will be checked before filing follow-ups.

Audit started on `job/instant-663`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Scope: documentation rules, mode × rule evidence matrix, locked perf-VM production/HDD measurements, shared-owner and adoption issues. No product code, push, deploy or product API changes. Existing #555, #549, #641, #642 and #640 will be checked before filing follow-ups.
Author
Owner

First source findings at base c4a61e8cf0; documentation commit 1a03eafcc:

  • Files: apps/web/src/lib/files/api.ts:19 uses 500-row pages; :145 drains pages into a resident collection. Server crates/plugins/files/src/lib.rs:2600 accepts up to 500. Signed keyset paging already exists in listing.rs:468 and must be reused.
  • Notes: crates/plugins/notes/src/lib.rs:3344 decodes a numeric offset as the cursor; :3364 uses OFFSET. Detail GET at :3568 reads and parses source bytes. Journal's committed projection and WAL reader work from #549 are useful, but do not cover general Note bodies.
  • Photos: crates/plugins/photos/src/index.rs:30 permits 90 days × 200 tiles. The day route uses an offset (routes.rs:653). The combined response does not satisfy a total 100-row cap.
  • Search: crates/calternal-server/src/main.rs:1023 explicitly has no final global cap; each provider can return 200. A fast provider deadline does not establish a total byte/row bound.
  • Money: crates/plugins/money/src/store.rs:432 checks and reparses changed Markdown on a read path; routes.rs:338 walks all Budget folders. This needs an Index projection, separate from the live blaze work.

Reuse/de-duplication: #641 comments identify job/blaze-settings (Settings and shared harness), job/blaze-surfaces (Files/Photos/Money/Tabs) and job/maillayouts (Mail). #642 and #640 already own their speed/layout paths. General Note link opening is also in #639. Follow-up adoption tasks will depend on these jobs and will not replace their active work.

Perf VM: bench/hdd-emu.sh mounted the direct-I/O dm-delay device with 8 ms configured read/write delay. The first QD1 qualification returned 84.377499 IOPS, p50 11.075584 ms, p99 33.161216 ms and failed the existing #549 qualification range. Load recorded under lock: 0.13/0.19/0.08. That failed qualification is retained; it is not a successful budget sample. A single bounded audit retry is in progress. No existing benchmark file or test expectation was changed.

First source findings at base c4a61e8cf090170f35b1bed3350d9de20c83ecd5; documentation commit 1a03eafcc: - Files: `apps/web/src/lib/files/api.ts:19` uses 500-row pages; `:145` drains pages into a resident collection. Server `crates/plugins/files/src/lib.rs:2600` accepts up to 500. Signed keyset paging already exists in `listing.rs:468` and must be reused. - Notes: `crates/plugins/notes/src/lib.rs:3344` decodes a numeric offset as the cursor; `:3364` uses `OFFSET`. Detail GET at `:3568` reads and parses source bytes. Journal's committed projection and WAL reader work from #549 are useful, but do not cover general Note bodies. - Photos: `crates/plugins/photos/src/index.rs:30` permits 90 days × 200 tiles. The day route uses an offset (`routes.rs:653`). The combined response does not satisfy a total 100-row cap. - Search: `crates/calternal-server/src/main.rs:1023` explicitly has no final global cap; each provider can return 200. A fast provider deadline does not establish a total byte/row bound. - Money: `crates/plugins/money/src/store.rs:432` checks and reparses changed Markdown on a read path; `routes.rs:338` walks all Budget folders. This needs an Index projection, separate from the live blaze work. Reuse/de-duplication: #641 comments identify `job/blaze-settings` (Settings and shared harness), `job/blaze-surfaces` (Files/Photos/Money/Tabs) and `job/maillayouts` (Mail). #642 and #640 already own their speed/layout paths. General Note link opening is also in #639. Follow-up adoption tasks will depend on these jobs and will not replace their active work. Perf VM: `bench/hdd-emu.sh` mounted the direct-I/O dm-delay device with 8 ms configured read/write delay. The first QD1 qualification returned 84.377499 IOPS, p50 11.075584 ms, p99 33.161216 ms and failed the existing #549 qualification range. Load recorded under lock: 0.13/0.19/0.08. That failed qualification is retained; it is not a successful budget sample. A single bounded audit retry is in progress. No existing benchmark file or test expectation was changed.
Author
Owner

Audit and plan delivered

Added the nine owner rules to DESIGN §58 and a short pointer under CLAUDE.md Performance review. Two atomic documentation commits: 1a03eafcc47c320479dd3f1a342bd266410ab59c and e62249dedcc2c7d108e4432596d40aee6f5a4bc8. No product code, package versions, migrations or lockfiles changed. No push or deployment.

Branch job/instant-663; source audit base c4a61e8cf090170f35b1bed3350d9de20c83ecd5; head e62249dedcc2c7d108e4432596d40aee6f5a4bc8. The required one-time git fetch origin and git merge origin/dev completed before gates: Already up to date. No job branch was merged into dev.

Files: docs/DESIGN.md:2705 (new §58) and CLAUDE.md:86 (Performance review). The new text was re-read after editing. It uses the glossary names and gives caches the existing access and source-of-truth constraints.

Method and scope

The matrix covers Calendar, Notes, Files, Photos, Mail, Money, Settings, Search and Admin × rules 1–9. Each cell below has an endpoint, source line and numeric evidence/reference. F means a structural gap in the full interaction. F† means acceptance is not established by the available measurements; it is not a claim of a measured latency violation. A partial implementation or fast endpoint does not count as a full-rule Pass. No full-rule Pass is established for a complete surface; partial working pieces are recorded as reuse.

Source links are fixed to the audit base, not mutable dev. Runtime measurements use the permitted shared release binary, with older server source cc25c441b7a974185622a1dee853cf38686d2b67, binary SHA-256 2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9, and its embedded production SPA. The separately built current web production bundle passed but was not substituted into the HDD run. These source/runtime revisions must not be treated as equivalent.

The server, Home and Index ran on root@10.69.69.63 with bench/hdd-emu.sh: direct-I/O loop, ext4, dm-delay 8 ms read/write, 200 read/write IOPS and 150 MiB/s caps. Every measured setup, qualification, API phase and UI sample held flock -w 14400 /root/perf.lock. Load was read inside the lock. Browser/request client ran on the shared build host through SSH and the HTTPS front; end-to-end times include that transport. CPU/RSS include queued indexing work, not only one handler. No outlier was removed.

Fixture: 366 Daily notes, 10,980 Logs, 30 daily recurring Events, 100 Photos, 100 Files, 20 Notes, 20 Tasks, three Budgets and 100 transactions. Mail is empty; Admin has one synthetic User. This is a realistic Calendar workload but a small fixture for other surfaces. It is not the requested largest-data acceptance suite.

The audit reuses the #549 harness through two temporary scripts under ignored artifacts/instant-663/; these add read-only endpoint probes and retain failures. No checked-in benchmark/test was modified. A successful API phase has five serial reads after fixture readiness and one five-request burst. With n=5, nearest-rank p95 equals max; this is a small-sample diagnostic. No mutation or Note-body acceptance sample is available.

HDD qualification

Phase QD IOPS p50 ms p99 ms Load in lock Result
First qualification 1 84.377499 11.075584 33.161216 post-failure inspection 0.13/0.19/0.08 Failed #549 range; retained
First valid phase 1 115.251300 8.028160 16.908288 0.10/0.18/0.08 Qualified
First valid phase 16 200.651986 96.993280 120.061952 0.20/0.20/0.09 Qualified
Correct-route read phase 1 125.008329 8.028160 8.159232 0.74/1.66/1.40 Qualified
Correct-route read phase 16 200.892560 100.139008 104.333312 0.73/1.60/1.38 Qualified

The temporary audit copy labeled sub-100 IOPS as slow rather than removing those samples, but the successful phases also met the unchanged #549 qualification range. The checked-in range assertions were not changed.

Representative measured reads

Each M reference is reused by that surface's cells as a representative read measurement, not proof of a different detail/action path or a complete UI rule. The cell's numeric code bound is separate. All listed serial/burst responses were HTTP 200. None of these nine endpoint types returned an HTTP ETag (0/9); that does not prove every endpoint lacks one. Settings/Admin reads do not save their contents.

Ref / surface Endpoint Serial median/p95/max ms (n=5) Burst median/p95/max ms (n=5) Serial CPU ms / RSS bytes Response bytes / summary rows Load in lock Baseline API p50/p95 ms
M1 Calendar /api/v1/calendar/range?from=2026-09-18&to=2026-10-02&tz=UTC 64.2/518.9/518.9 107.6/123.7/123.7 290 / 286105600 254474 / 15 3.1/1.9/0.84 1.7/4.0 range
M2 Notes /api/v1/notes?limit=100 52.2/69.6/69.6 124.7/125.4/125.4 570 / 422936576 12660 / 100 2.7/2.43/1.81 1.3/3.1 list
M3 Files /api/v1/files/entries?limit=100 87.8/1069.4/1069.4 2033.2/2034.0/2034.0 1090 / 443625472 2756 / 11 3.32/1.98/0.88 1.8/3.1 entries
M4 Photos /api/v1/photos/timeline?days=30&tiles_per_day=48 46.0/48.2/48.2 62.2/62.7/62.7 10 / 443625472 15487 / 30 3.45/2.03/0.9 1.4/3.9 timeline
M5 Mail /api/v1/mail/inbox/messages?limit=100 41.1/48.0/48.0 53.8/54.0/54.0 20 / 444309504 24 / 0 3.66/2.1/0.93 no matching Inbox profile
M6 Money /api/v1/money/budgets/{id}/accounts 44.7/50.8/50.8 45.1/45.4/45.4 80 / 444309504 314 / 1 3.66/2.1/0.93 no matching profile
M7 Settings /api/v1/auth/me/security 482.2/1266.5/1266.5 86.4/89.9/89.9 290 / 511967232 67 / not a list 3.92/2.18/0.96 no matching security profile
M8 Search /api/v1/search?q=log&limit=100&semantic=false 49.3/55.3/55.3 52.1/54.2/54.2 130 / 515657728 31009 / 100 3.69/2.16/0.96 no matching keyword API profile
M9 Admin /api/v1/auth/users 140.4/929.4/929.4 81.6/82.2/82.2 220 / 515657728 198 / 1 3.79/2.21/0.98 no matching Users profile

Baseline is docs/perf/baseline.json, commit 369ab6a2f9fc673e3564b94857fbecfeb04df404, recorded 2026-09-29. It uses another fixture/build/transport and 50 API repetitions. Its browser route profile uses 4× CPU throttle. The values are shown for context; no controlled regression ratio or threshold claim is valid. No baseline was replaced. A future same-workload regression gets its own issue.

Notes M2 uses the corrected phase. The initial /api/v1/notes/?limit=100 returned five 404s (median/p95/max 43.6/51.3/51.3 ms); that was the audit's wrong URI, not Note performance. Those results are retained and excluded from M2. The corrected phase completed eight endpoint profiles, then stopped on an Admin burst get: socket hang up before the Note-detail probe. #705 owns diagnosis. It is neither a timeout sample nor evidence of a proved server crash. No kernel OOM entry was found; instantaneous health/process exit were not captured. The original phase's Admin M9 is valid and remains separate.

Retained Tab diagnostics

The production UI run saved eight samples before requested Tab did not become selected. The retained condition counts are cold Calendar→Photos n=3, cold Photos→Calendar n=3, and each warm direction n=1. No condition has five samples; no acceptance p95 is claimed. The error is retained with the #549/#641 benchmark gap.

Direction/state n Calendar DOM median/max ms Full target paint median/max ms
calendar→photos cold 3 not a Calendar target 1524.5/1568.9
photos→calendar cold 3 1390.1/2291.1 3561.0/6304.5
photos→calendar warm 1 401.1/401.1 3423.8/3423.8
calendar→photos warm 1 not a Calendar target 573.1/573.1

New first visits were Calendar 4239.4 ms fully painted and Photos 1789.7 ms (one sample each, not a percentile). Browser-host load during retained switches was 12.5–19.75 (1 min); VM load was 2.38–3.29. Older #549's 11 warm Chromium phone Calendar DOM samples had p95 229.5 ms. Its old First/Full counter race is documented; only target-boundary-safe marks are usable. These results use different source/load/fixture conditions and are not a before/after ratio.

Mode × rule matrix

R1 Index/precompute; R2 bounded keyset/window; R3 header/revision/ETag; R4 optimistic client-ID/receipt/Undo; R5 one User stream/delta; R6 retained snapshot; R7 virtualization/narrow rows/non-await keys; R8 background/count/read priority; R9 production/HDD budgets.

Surface R1 R2 R3 R4 R5 R6 R7 R8 R9
Calendar F F F F F F F F F†
Notes F F F F F F F F F†
Files F F F F F F F† F F†
Photos F F F F F F F F F†
Mail F F F F F F F F F†
Money F F F F F F F F F†
Settings F F F F F F F F F†
Search F F F F F F F F F†
Admin F F F F F F F F F†

Cell evidence

The source observation and measured M reference are separate evidence. R9 F† explicitly covers missing cached-open/action/first-usable/blaze and browser/device/large-fixture measurements. Rows below do not imply an API-read latency is the corresponding UI-action latency.

Calendar — M1

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/calendar/range crates/plugins/calendar/src/feeds/subscriptions.rs:1428 Loads Markdown feed definitions on each range. view.rs:151 also expands iCalendar on request. 20,000 external day-item cap does not remove parsing. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #677
R2 F GET /api/v1/calendar/range crates/plugins/calendar/src/view.rs:35 A 366-day bound is not a 100-item/byte page. External items can reach 20,000; details and counts share the response. items.rs already supplies a cursor for saved items. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #678
R3 F GET /api/v1/calendar/range apps/web/src/lib/calendar/data.ts:77 4 range/6 grid/2 year caches are time-keyed, not a revision-keyed body cache with strong conditional reads. Reuse #549. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #665 → #669
R4 F POST /api/v1/notes/journal/log/batch apps/web/src/lib/calendar/edits.ts:86 Pending client IDs exist, but UndoStep at :307 is a client closure. No common durable receipt and inverse contract. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #667 → #669
R5 F GET /api/v1/files/events apps/web/src/lib/calendar/data.ts:98 Shares the Files path-hint stream; Notes paths clear all snapshots. No app-wide bounded delta preserves unchanged items. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #668 → #669
R6 F GET /api/v1/calendar/range apps/web/src/lib/calendar/data.ts:88 Complete grids retained, but caches bound entries, not bytes/rows, and omit a shared selection/scroll snapshot contract. Existing #549 is partial reuse. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #666 → #669
R7 F GET /api/v1/calendar/range packages/ui/src/components/calendar/AgendaList.svelte:1 Agenda defers mounting but keeps visited rows; #549 says no row recycling. Full blaze/frame and narrow-row invariant is not established. Existing #641 owns traversal. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #641
R8 F GET /api/v1/calendar/range crates/plugins/calendar/src/view.rs:579 Range computes detailed aggregation and awaits thumbnail enqueue before response; read-only pools exist but counts/work are still on first usable path. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #679
R9 F† /today ↔ /photos bench/tab-switch.mjs:932 New locked run has ≥5 cold/warm samples; first usable 10k and cached/action/blaze budgets still require distinct markers. Older #549 DOM p95 229.5 ms at phone warm misses 100 ms. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. #549 / #641

Notes — M2

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/notes/{id} crates/plugins/notes/src/lib.rs:3568 General Note GET uses store::read and view parsing on the request. Journal source projection from #549 does not cover this body. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #702
R2 F GET /api/v1/notes crates/plugins/notes/src/lib.rs:3344 Numeric cursor is OFFSET; API cap is 100 but bytes are not capped. noteIndex.svelte.ts:51 drains every page into byId. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #703
R3 F GET /api/v1/notes/{id} apps/web/src/lib/api/notes.ts:18 Fetches Note detail each open; JSON etag is not a shared strong HTTP ETag/304 body cache. Existing #639 owns linked-open speed. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #665 → #701
R4 F POST /api/v1/notes apps/web/src/routes/notes/+page.svelte:57 Create awaits response before item publication. Conditional writes exist, but no shared durable client-ID receipt/Undo contract. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #667 → #701
R5 F GET /api/v1/notes apps/web/src/lib/notes/noteIndex.svelte.ts:51 Whole-index reload and Files/Notes invalidation are not the shared changed-since sequence plus capped delta. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #668 → #701
R6 F GET /api/v1/notes apps/web/src/routes/notes/+page.svelte:40 Route owns list/page state; no shared retained rows/cursors/scroll snapshot. General body restoration remains in #639/#641 scope. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #666 → #701
R7 F GET /api/v1/notes apps/web/src/lib/components/NoteList.svelte:59 NoteList renders every loaded item at :59; 50-row pages accumulate. Narrow recycled rows and zero incomplete blaze frames are not established; use #641/#639. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #641 / #639
R8 F GET /api/v1/notes apps/web/src/lib/notes/noteIndex.svelte.ts:51 Navigator/index metadata drains all title pages. General Note GET parses on request; separate reader pool already exists for list queries. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #704
R9 F† GET /api/v1/notes bench/tab-switch.mjs:620 Correct-route list p50/p95/max 52.2/69.6/69.6 ms (n=5) is not cached body/open/action/10k acceptance. Initial wrong trailing-slash 404 was a harness defect; corrected phase stopped before body sampling. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. #549 / #641

Files — M3

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/files/download?inline=true crates/plugins/files/src/lib.rs:3664 Indexed folder list already passes the projection-only read path, but Quick Look TextView fetches downloadUrl (viewer.ts:16), streaming Markdown source from disk under the mutation lock. UI text preview must use a committed indexed body; keep original-byte download semantics separate. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #699
R2 F GET /api/v1/files/entries apps/web/src/lib/files/api.ts:19 Client PAGE=500, server permits 500 (lib.rs:2600); listAll drains all pages. Signed keyset cursor already exists at listing.rs:468; no total byte bound. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #680
R3 F GET /api/v1/files/entries apps/web/src/lib/files/api.ts:41 8 first pages and 30 s freshness are not revision/byte-keyed bodies. Download conditional reads at lib.rs:3648 are reuse, not coverage for listings/stat. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #665 → #670
R4 F POST /api/v1/files/trash apps/web/src/lib/files/FilesBrowser.svelte:714 Row removal follows awaited trash. transfer.ts:138 supplies Undo closures; no durable client-ID receipt/inverse shared with other views. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #667 → #670
R5 F GET /api/v1/files/events crates/plugins/files/src/lib.rs:3834 SSE sends per-path events (256/replay batch, ten-minute retention); client live.ts:23 refetches rather than pulls app-wide deltas. Durable /changes exists to reuse. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #668 → #670
R6 F GET /api/v1/files/entries apps/web/src/lib/files/api.ts:39 8 first-page cache entries omit full resident window, cursor chain, selection/scroll and byte limit. Use #549 as seed. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #666 → #670
R7 F† GET /api/v1/files/entries apps/web/src/lib/files/FilesBrowser.svelte:241 Collection virtualizes DOM, but listAll still accumulates the entire folder. Same-frame preview and two-row highlight proof remain in active #641 blaze-surfaces. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #641
R8 F GET /api/v1/files/entries crates/plugins/files/src/listing.rs:259 COUNT precedes page query; listing.rs:527 reads all outgoing shares for badges. Reader pool exists, but unrelated count/badge work remains before first rows. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #681
R9 F† GET /api/v1/files/entries bench/tab-switch.mjs:621 Locked API probe uses a small Home root, not 10k folder first paint or action/cache/blaze. Existing #641/#549 own surface performance verification. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. #549 / #641

Photos — M4

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/photos/timeline crates/plugins/photos/src/routes.rs:440 Timeline metadata is indexed, but active_roots reads settings from the filesystem on each request (:440) and detail stats the file (:987). Precompute/cache authorized root preferences and detail header projection; image original streams remain file bytes. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #700
R2 F GET /api/v1/photos/timeline crates/plugins/photos/src/index.rs:30 90 days × 200 tiles permits 18,000 tiles. timeline/days uses unsigned offset (:653); buckets returns all days (:613). No total 100-row/byte keyset page. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #682
R3 F GET /api/v1/photos/timeline apps/web/src/lib/photos/timeline.svelte.ts:307 Timeline filter cache is not a revision-keyed body cache; no strong conditional timeline response. Full-image predecode work belongs to active #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #665 → #671
R4 F PUT /api/v1/tags/items apps/web/src/lib/photos/PhotosView.svelte:611 Per-item sidecar writes are sequential; no common synchronous mutation/client-ID receipt/Undo contract across Files and Photos. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #667 → #671
R5 F GET /api/v1/photos/timeline/buckets apps/web/src/lib/photos/PhotosView.svelte:742 Refresh timers at 900 and 3500 ms rebuild buckets. No shared delta merges with stable unchanged object identity. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #668 → #671
R6 F GET /api/v1/photos/timeline apps/web/src/lib/photos/timeline.svelte.ts:307 Cached filter stores retain data, but loaded Map (:48) has no shared row/byte-bounded view snapshot with selection/scroll. Decoded-image LRU belongs to #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #666 → #671
R7 F GET /api/v1/photos/items/{id} apps/web/src/lib/photos/PhotoViewer.svelte:229 Grid/viewer already use shared display primitives. The #641 source trace found collection-wide URL/date mapping per selection; fixes remain on its active branch. Zero-incomplete traversal not proved. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #641
R8 F GET /api/v1/photos/timeline/buckets crates/plugins/photos/src/routes.rs:629 All day counts are fetched before first timeline use. Ingest batches 32/200 (index.rs:430/:519) and uses workers, but foreground counts remain part of startup. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #683
R9 F† /photos ↔ /today bench/tab-switch.mjs:932 Locked Tab samples use 100 Photos. First usable 10k/50k and cache/action/blaze are not covered by that smoke; reuse active #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. #549 / #641

Mail — M5

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/mail/messages/{id}/attachments/{section_id} crates/plugins/mail/src/routes.rs:1479 Attachment open connects to IMAP in the handler. message_detail (:1404) extracts links and can sanitize raw HTML on each read. Inbox headers already use the Index. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #684
R2 F GET /api/v1/mail/inbox/messages crates/plugins/mail/src/routes.rs:1128 Messages capped at 100 but cursors are unsigned before_received_ms/before_id. No response byte cap; thread attachment query cache/store.rs:1484 permits 500 rows. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #685
R3 F GET /api/v1/mail/messages/{id} apps/web/src/lib/mail/MailView.svelte:384 Detail open awaits a new fetch; no common revision cache/strong conditional body contract. #640 already owns reading layouts and neighbour prefetch. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #665 → #672
R4 F POST /api/v1/mail/messages/{id}/read-state apps/web/src/lib/mail/MailView.svelte:238 Updates rows after awaited POST, not one synchronous commit. Existing provider sync queue is not a User-visible client-ID mutation receipt and durable Undo. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #667 → #672
R5 F GET /api/v1/mail/inbox/messages apps/web/src/lib/mail/MailView.svelte:227 30,000 ms inbox poll; no app-wide changed-since stream/delta. New-mail notice preserves current reading position and must remain. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #668 → #672
R6 F GET /api/v1/mail/inbox/messages apps/web/src/lib/mail/MailView.svelte:330 Reload obtains accounts, folders, list and detail. #640/#641 have newer folder/body caches; integrate those before adapting shared snapshots. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #666 → #672
R7 F GET /api/v1/mail/inbox/messages apps/web/src/lib/mail/MailView.svelte:600 Base list renders every loaded thread row. #640 owns virtualized reader layouts; #641 measures held-key completeness. Do not duplicate active work. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #641 / #640
R8 F GET /api/v1/mail/accounts apps/web/src/lib/mail/MailView.svelte:330 Accounts await folders before message first paint. Precomputed folder counts and reader_pool exist; defer non-selected folder/count work and verify reads during backfill. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #686
R9 F† GET /api/v1/mail/inbox/messages bench/tab-switch.mjs:88 Audit fixture Mail is empty (0 rows, 24 bytes). No large-mailbox/body latency or blaze proof; #640/#641/#626 own relevant active runs. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. #549 / #641 / #640

Money — M6

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/money/budgets/{id}/accounts crates/plugins/money/src/store.rs:432 Changed source is read and parsed under the User lock on a request. Kernel-identity parse cache is reuse, not an indexed committed projection. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #687
R2 F GET /api/v1/money/budgets/{id}/transactions crates/plugins/money/src/routes.rs:916 Returns all matching transactions; no cursor/row/byte bound. Budgets (:338) and accounts (:612) also return complete sets. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #688
R3 F GET /api/v1/money/budgets/{id}/accounts apps/web/src/lib/money/api.ts:18 no-store reads; one last account answer retained, not revision-keyed register/body cache with conditional reads. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #665 → #673
R4 F PUT /api/v1/money/budgets/{id}/transactions/{transaction_id}/cleared apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:133 Waits for state write then full load. No durable receipt/client-ID inverse and synchronous cross-view selection update. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #667 → #673
R5 F GET /api/v1/money/budgets/{id}/accounts apps/web/src/lib/money/store.svelte.ts:87 refresh re-reads account answers after local write; no cross-Installation changed-since stream/delta for register or Budget. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #668 → #673
R6 F GET /api/v1/money/budgets/{id}/accounts apps/web/src/lib/money/store.svelte.ts:57 Budget switch sets accounts=null; one account set retained, no shared register/cursor/scroll LRU. Active #641 bounded neighbour work must be reused. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #666 → #673
R7 F GET /api/v1/money/budgets/{id}/transactions apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:291 Register each block renders all transactions; active #641 owns account-arrow and prefetch work but does not establish a globally bounded register. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #641
R8 F GET /api/v1/money/budgets/{id}/accounts crates/plugins/money/src/routes.rs:612 Request loads ledger and replays totals rather than reads precomputed committed answers. Shared User read lock can wait for writes; no separate Index reader projection. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #689
R9 F† GET /api/v1/money/budgets/{id}/accounts apps/web/src/lib/money/store.svelte.ts:6 Five locked API samples cover a small fixture only. Active #641 reports incomplete account steps; no first usable 10k register or accepted/durable action proof. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. #549 / #641

Settings — M7

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/appearance crates/calternal-server/src/appearance.rs:739 Reads and projects settings.json on a UI read. Auth/security reads use SQLite, but not all Settings cards use a precomputed Index projection. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #690
R2 F GET /api/v1/auth/sessions crates/calternal-auth/src/store.rs:2043 Sessions returns a complete Vec with no cursor/byte cap. Fixed Settings navigation labels are bounded; sessions/App Passwords lists can grow. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #691
R3 F GET /api/v1/auth/me/security apps/web/src/routes/settings/api.svelte.ts:74 Resource load publishes a fresh value without revision/ETag cache. #642 adds safe per-User card snapshots on its branch; reuse before common adoption. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #665 → #674
R4 F PUT /api/v1/appearance apps/web/src/routes/settings/api.svelte.ts:23 withStepUp awaits a definitive action. Safe preferences lack shared mutation receipt/Undo; security changes must keep step-up and must not optimistically grant authority. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #667 → #674
R5 F GET /api/v1/appearance apps/web/src/lib/stores/settings-store.ts:1 Local per-User preference state exists, but no shared stream/delta carries preference changes across Installations and retained views. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #668 → #674
R6 F /settings/account apps/web/src/routes/settings/[...path]/+page.svelte:3 Overlay owns visited section lifecycle; base Resource loads per mount. #642 retained safe resources and mounted sections are partial reuse, not shared byte LRU. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #666 → #674
R7 F GET /api/v1/auth/sessions apps/web/src/routes/settings/account/SessionsGroup.svelte:88 Sessions renders every loaded entry. Static rail itself does not need virtualization. #641 owns rail blaze navigation and latest-intent frame sampler. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #641 / #642
R8 F GET /api/v1/auth/me/security crates/calternal-auth/src/store.rs:2043 Auth reads share store.pool instead of a separate read-only pool; base Settings cards start their data reads on mount. #642 scheduling work is active reuse. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #692
R9 F† /settings/account bench/settings-shortcut.mjs:1 Five locked security-read samples are not full-card opening latency. #642 final local warm content 377.4 ms and cold 1103.9 ms are not locked HDD acceptance. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. #549 / #641 / #642

Search — M8

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/search crates/plugins/calendar/src/search.rs:76 Calendar provider parses legacy iCalendar tags when tags_json is absent; Search Log preview calls loadRange (SearchPreview.svelte:142), which loads Markdown feed definitions. Indexed keyword lookup alone is not full preview coverage. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #693
R2 F GET /api/v1/search crates/calternal-server/src/main.rs:1023 No final global cap; up to 200 hits per provider, no signed result-window cursor or response byte cap. Bounded plugin lists do not bound the combined response. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #694
R3 F GET /api/v1/search apps/web/src/lib/search/SearchPreview.svelte:173 80-entry cache keyed only by target.id, with no revision/byte/User key or session-clear handler in this module. Needs #555 lifecycle and #665. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #665 → #675
R4 F POST/PATCH/DELETE /api/v1/search/saved apps/web/src/lib/search/saved.svelte.ts:125 Saved-search create/update/remove await the API before changing items (:126/:133/:140). No client-ID durable receipt/Undo or synchronous retained-query update. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #667 → #675
R5 F GET /api/v1/search apps/web/src/lib/search/window.svelte.ts:408 Queries pull full answers and saved counts are refreshed separately. No shared delta applies changed IDs/revisions while keeping unchanged hits by identity. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #668 → #675
R6 F /search?q=… apps/web/src/lib/search/window.svelte.ts:1 Query state is retained in the window, but no shared byte/row LRU for query/facet/result cursor, preview and scroll. Existing 80-preview cache is partial reuse. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #666 → #675
R7 F GET /api/v1/search apps/web/src/lib/search/SearchPreview.svelte:180 Preview debounces each uncached target by 70 ms, conflicting with every-step 15/33 ms blaze. Expanded result list is already virtualized above 60 (:182 in search-dialog). Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #641
R8 F GET /api/v1/search apps/web/src/lib/search/server.ts:145 Keyword first then hybrid after 80 ms is reuse; base Notes metadata loader still drains corpus and providers perform request-time detail work. Background read priority not established for all providers. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #695
R9 F† GET /api/v1/search bench/search-group-rows.mjs:1 Five locked keyword API reads are not 100k hybrid results or every-frame preview completeness. #641 owns shared blaze targets; add Search adapter after its harness lands. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. #549 / #641

Admin — M9

Rule / result Endpoint Source at audit base Evidence and numeric bound Owner
R1 F GET /api/v1/admin/config/toml crates/calternal-server/src/wire.rs:4018 Raw TOML read comes from filesystem in request. Parsed config GET at :3739 already uses live in-memory state; do not persist raw config in browser caches. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #696
R2 F GET /api/v1/auth/users crates/calternal-auth/src/store.rs:1611 Unbounded SELECT * and fetch_all for Users. No keyset cursor or byte cap; Jobs already has bounded list support to reuse. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #697
R3 F GET /api/v1/auth/users apps/web/src/routes/settings/admin/UsersGroup.svelte:34 Resource fetch has no shared revision/conditional-read contract. Cache only public summary fields after authorization; raw TOML and secrets stay out of persistent caches. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #665 → #676
R4 F PATCH /api/v1/auth/users/{id}/role apps/web/src/routes/settings/admin/UsersGroup.svelte:69 State-changing operations await step-up and refresh. Shared receipts are absent; safe pending UI must not grant authority or claim purge/revoke is undone. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #667 → #676
R5 F GET /api/v1/auth/users apps/web/src/routes/settings/admin/UsersGroup.svelte:34 No cross-Installation sequence/delta for User summaries, plugin switches or Jobs; enforce Admin role again at each delta read and revoke retained state. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #668 → #676
R6 F /settings/admin/users apps/web/src/routes/settings/api.svelte.ts:74 Cards own Resource state rather than shared complete view snapshots. #642 Admin authorization and memory-only raw TOML lifecycle must remain. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #666 → #676
R7 F GET /api/v1/auth/users apps/web/src/routes/settings/admin/UsersGroup.svelte:136 Renders all User rows. No narrow virtualization for large Instances; rail traversal belongs to #641/#642 and must not be duplicated. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #641 / #642
R8 F GET /api/v1/auth/users crates/calternal-auth/src/store.rs:1611 Auth queries use same pool as writes and return all users before paint. Core Db reader_pool at db.rs:69 is existing shared infrastructure to use. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #698
R9 F† GET /api/v1/auth/users bench/tab-switch.mjs:88 Five locked reads cover one synthetic User only. No large Users/Jobs list paint, action/Undo or blaze acceptance; #641/#642 supply harness and overlay work. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. #549 / #641 / #642

Filed work and ownership

One shared owner per primitive: #665 revision cache + ETag/304; #666 view-snapshot LRU; #667 optimistic mutation/client IDs/durable inverse receipts; #668 per-User stream + capped deltas. Each has a self-contained context, source and measurement evidence, expected behavior, regression tests and performance acceptance.

Each surface has one shared-contract adoption issue, plus a rule-specific issue for each remaining R1/R2/R8 gap. Shared R3–R6 fixes are grouped under one primitive owner rather than multiplied per surface. R7/R9 retain the existing #641/#549 and surface speed owners; new adoption tests use their harness and integrate their branch results. No duplicate blaze/layout/opening jobs were filed.

Surface Adopt R3–R6 R1 projection/read path R2 paging/window R8 first-paint/read priority
Calendar #669 #677 #678 #679
Notes #701 #702 #703 #704
Files #670 #699 #680 #681
Photos #671 #700 #682 #683
Mail #672 #684 #685 #686
Money #673 #687 #688 #689
Settings #674 #690 #691 #692
Search #675 #693 #694 #695
Admin #676 #696 #697 #698

#705 is the separate non-SLOW transport triage. Total: 41 new issues = four shared owners + nine adopters + 27 rule-specific tasks + one transport investigation. None was closed.

Reuse and active work

  • #555 userStorage owns User keying/session-end cleanup. Do not add another browser persistence module.
  • #549 route cache, complete Calendar grid/Agenda snapshots, Journal committed-source projection, generation guards and target-paint harness. Retain its Notes/Files correctness fixes and #627 destination-identity work.
  • Files signed keyset pages, durable change feed, #452 User stream isolation, existing strong download/thumbnail ETags and Undo journals. Files R2 #680 owns any minimum public signed-cursor/bounds extraction. Other modes adopt it.
  • Core Db::reader_pool already exists (crates/calternal-db/src/db.rs:69). Auth currently uses a separate store pool for reads/writes; adopt, do not duplicate core read machinery. Admin R2 #697 owns the shared Auth paging addition; Settings #691 adds its User-scoped adapters.
  • #641 comments identify job/blaze-settings (harness and Settings), job/blaze-surfaces (Files/Photos/Money/Tabs), and job/maillayouts (Mail). Latest Settings closeout is bf3ad5f29…; Mail #640 closeout f9f360e68…; blaze-surfaces follow-up comments still trace Photos/Money/Tabs. These branches are reuse/dependencies, not claims that their code is on this audit base.
  • #642 remains Settings opening/resource/overlay owner; #640 remains Mail layouts/neighbor body cache owner. #639 owns linked Note opening. #613/#614/#626 own provider sync and UID work. #661/#634 and Notes restart issues own Note-write correctness.
  • Generic file-text preview belongs to Files #699; Note/collaboration body publication belongs to Notes #702. Photos #700 adopts Settings #690 preference projection. Search #695 adopts Notes #704 metadata bounds rather than changing the same loader in two jobs.

Gates (verbatim output)

cargo fmt --check: exit 0, no output. git diff --check: exit 0, no output. No Rust crate, route, contract or web product code changed; per-crate clippy/test and API adversarial merge gates are not applicable to these documentation-only commits. The read-only measurement bursts were diagnostic probes, not a claim of a full adversarial round.

bun run check (exit 0):

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/instant-663/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test (exit 0; summary quoted verbatim):

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  07:34:21
   Duration  158.13s (transform 52%, environment 19%, import 13%, tests 12%, setup 4%)

The existing jsdom Window's scrollTo() messages remain; no assertion or fixture was changed.

bun run build (exit 0; final output excerpt):

✓ built in 56.00s

Run npm run preview to preview your production build locally.

> Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done

cargo clean (exit 0):

     Removed 1 file, 356B total

Generated apps/web/build and apps/web/.svelte-kit were deleted. Review data stay under ignored artifacts/instant-663/. No screenshots, logs or data were committed.

Decisions

  • Treat a rule as a full-interaction conjunction. A partial indexed list/cache is reuse, not a complete Tab Pass. Use F† for a missing acceptance measurement instead of inventing a result. This audit classification is not a new product behavior.
  • Use the required shared release binary as measured, record its exact older source/hash, and keep the newer static source audit separate. Do not replace the shared baseline or claim a controlled ratio.
  • Group shared R3–R6 into four owner issues and one adopter per surface; keep R1/R2/R8 as rule-specific jobs and R7/R9 with the active benchmark/surface owners. This prevents duplicate primitive owners.
  • Original binary downloads keep their byte-stream semantics. Interactive Markdown text previews need an indexed source; the Files/Notes issues divide that implementation. No new storage format, byte cap value, cache TTL, receipt retention or migration number is chosen in this audit. Those implementation choices must be recorded by their owner issue.

UX gaps closed / left

Closed: no product UX change in this audit-only job. The plan now assigns cached restoration, same-frame cross-view mutations, durable Undo, Copy link/focus/touch/screen-reader tests and real empty/error/offline states to the appropriate owners.

Left: the actual product gaps in the 81 cells await those jobs. No complete 10k first-usable/cached-open/action/blaze matrix, full-size mailbox/register/folder fixture, all widths/themes/engines, or Note-detail timing was measured here. The interrupted UI and Admin transport cases remain explicit; no spinner/cache/action claim is called proved from an API p95.

Audit artifacts

Artifacts are ignored and remain local for review; the measurements, matrix and issue mapping above are the durable issue record. Files:

  • artifacts/instant-663/audit-hdd.mjs SHA-256 fd38d44c13c5481ef2b1e68a519c8b3620a1b112a4812c8766f8ca20f6fa79f9
  • artifacts/instant-663/audit-reads.mjs SHA-256 b998f6551c5abe684ac59d7b98be55094855b8a93e8bb156ddee9960bdf3f9ba
  • artifacts/instant-663/api-hdd.json SHA-256 b129bc494dab4171f6882911ed86bb625b0e5234d4b5da516f533662b4136ac1
  • artifacts/instant-663/api-hdd-corrected-partial.json SHA-256 e7983fe87cbf1d9ad8620ddda7875c4a99f49f981f6f9947cc10d7f887daed35
  • artifacts/instant-663/production-hdd.json SHA-256 38c2011b2820c3da281dcf490183d44da4eb4717b0973838491ea3509a1fa3ee
  • artifacts/instant-663/cells.json SHA-256 6b1fe7347a048338615e4b393cabd8e8ee652e58c5e94e203772976afdd24729

The audit source was derived from bench/tab-switch.mjs, adding the listed five-read/burst endpoint probes; audit-reads.mjs corrects the Notes URI and stops after server probes. The complete sampled numbers and stopped-phase reasons are retained above.

## Audit and plan delivered Added the nine owner rules to DESIGN §58 and a short pointer under CLAUDE.md Performance review. Two atomic documentation commits: `1a03eafcc47c320479dd3f1a342bd266410ab59c` and `e62249dedcc2c7d108e4432596d40aee6f5a4bc8`. No product code, package versions, migrations or lockfiles changed. No push or deployment. Branch `job/instant-663`; source audit base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`; head `e62249dedcc2c7d108e4432596d40aee6f5a4bc8`. The required one-time `git fetch origin` and `git merge origin/dev` completed before gates: `Already up to date.` No job branch was merged into dev. Files: `docs/DESIGN.md:2705` (new §58) and `CLAUDE.md:86` (Performance review). The new text was re-read after editing. It uses the glossary names and gives caches the existing access and source-of-truth constraints. ## Method and scope The matrix covers Calendar, Notes, Files, Photos, Mail, Money, Settings, Search and Admin × rules 1–9. Each cell below has an endpoint, source line and numeric evidence/reference. `F` means a structural gap in the full interaction. `F†` means acceptance is not established by the available measurements; it is not a claim of a measured latency violation. A partial implementation or fast endpoint does not count as a full-rule Pass. No full-rule Pass is established for a complete surface; partial working pieces are recorded as reuse. Source links are fixed to the audit base, not mutable dev. Runtime measurements use the permitted shared release binary, with **older server source `cc25c441b7a974185622a1dee853cf38686d2b67`**, binary SHA-256 `2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9`, and its embedded production SPA. The separately built current web production bundle passed but was not substituted into the HDD run. These source/runtime revisions must not be treated as equivalent. The server, Home and Index ran on `root@10.69.69.63` with `bench/hdd-emu.sh`: direct-I/O loop, ext4, dm-delay 8 ms read/write, 200 read/write IOPS and 150 MiB/s caps. Every measured setup, qualification, API phase and UI sample held `flock -w 14400 /root/perf.lock`. Load was read inside the lock. Browser/request client ran on the shared build host through SSH and the HTTPS front; end-to-end times include that transport. CPU/RSS include queued indexing work, not only one handler. No outlier was removed. Fixture: 366 Daily notes, 10,980 Logs, 30 daily recurring Events, 100 Photos, 100 Files, 20 Notes, 20 Tasks, three Budgets and 100 transactions. Mail is empty; Admin has one synthetic User. This is a realistic Calendar workload but a small fixture for other surfaces. It is not the requested largest-data acceptance suite. The audit reuses the #549 harness through two temporary scripts under ignored `artifacts/instant-663/`; these add read-only endpoint probes and retain failures. No checked-in benchmark/test was modified. A successful API phase has five serial reads after fixture readiness and one five-request burst. With n=5, nearest-rank p95 equals max; this is a small-sample diagnostic. No mutation or Note-body acceptance sample is available. ### HDD qualification | Phase | QD | IOPS | p50 ms | p99 ms | Load in lock | Result | | --- | ---: | ---: | ---: | ---: | --- | --- | | First qualification | 1 | 84.377499 | 11.075584 | 33.161216 | post-failure inspection 0.13/0.19/0.08 | Failed #549 range; retained | | First valid phase | 1 | 115.251300 | 8.028160 | 16.908288 | 0.10/0.18/0.08 | Qualified | | First valid phase | 16 | 200.651986 | 96.993280 | 120.061952 | 0.20/0.20/0.09 | Qualified | | Correct-route read phase | 1 | 125.008329 | 8.028160 | 8.159232 | 0.74/1.66/1.40 | Qualified | | Correct-route read phase | 16 | 200.892560 | 100.139008 | 104.333312 | 0.73/1.60/1.38 | Qualified | The temporary audit copy labeled sub-100 IOPS as slow rather than removing those samples, but the successful phases also met the unchanged #549 qualification range. The checked-in range assertions were not changed. ## Representative measured reads Each M reference is reused by that surface's cells as a **representative read measurement**, not proof of a different detail/action path or a complete UI rule. The cell's numeric code bound is separate. All listed serial/burst responses were HTTP 200. None of these nine endpoint types returned an HTTP ETag (0/9); that does not prove every endpoint lacks one. Settings/Admin reads do not save their contents. | Ref / surface | Endpoint | Serial median/p95/max ms (n=5) | Burst median/p95/max ms (n=5) | Serial CPU ms / RSS bytes | Response bytes / summary rows | Load in lock | Baseline API p50/p95 ms | | --- | --- | --- | --- | --- | --- | --- | --- | | M1 Calendar | `/api/v1/calendar/range?from=2026-09-18&to=2026-10-02&tz=UTC` | 64.2/518.9/518.9 | 107.6/123.7/123.7 | 290 / 286105600 | 254474 / 15 | 3.1/1.9/0.84 | 1.7/4.0 range | | M2 Notes | `/api/v1/notes?limit=100` | 52.2/69.6/69.6 | 124.7/125.4/125.4 | 570 / 422936576 | 12660 / 100 | 2.7/2.43/1.81 | 1.3/3.1 list | | M3 Files | `/api/v1/files/entries?limit=100` | 87.8/1069.4/1069.4 | 2033.2/2034.0/2034.0 | 1090 / 443625472 | 2756 / 11 | 3.32/1.98/0.88 | 1.8/3.1 entries | | M4 Photos | `/api/v1/photos/timeline?days=30&tiles_per_day=48` | 46.0/48.2/48.2 | 62.2/62.7/62.7 | 10 / 443625472 | 15487 / 30 | 3.45/2.03/0.9 | 1.4/3.9 timeline | | M5 Mail | `/api/v1/mail/inbox/messages?limit=100` | 41.1/48.0/48.0 | 53.8/54.0/54.0 | 20 / 444309504 | 24 / 0 | 3.66/2.1/0.93 | no matching Inbox profile | | M6 Money | `/api/v1/money/budgets/{id}/accounts` | 44.7/50.8/50.8 | 45.1/45.4/45.4 | 80 / 444309504 | 314 / 1 | 3.66/2.1/0.93 | no matching profile | | M7 Settings | `/api/v1/auth/me/security` | 482.2/1266.5/1266.5 | 86.4/89.9/89.9 | 290 / 511967232 | 67 / not a list | 3.92/2.18/0.96 | no matching security profile | | M8 Search | `/api/v1/search?q=log&limit=100&semantic=false` | 49.3/55.3/55.3 | 52.1/54.2/54.2 | 130 / 515657728 | 31009 / 100 | 3.69/2.16/0.96 | no matching keyword API profile | | M9 Admin | `/api/v1/auth/users` | 140.4/929.4/929.4 | 81.6/82.2/82.2 | 220 / 515657728 | 198 / 1 | 3.79/2.21/0.98 | no matching Users profile | Baseline is `docs/perf/baseline.json`, commit `369ab6a2f9fc673e3564b94857fbecfeb04df404`, recorded 2026-09-29. It uses another fixture/build/transport and 50 API repetitions. Its browser route profile uses 4× CPU throttle. The values are shown for context; **no controlled regression ratio or threshold claim is valid**. No baseline was replaced. A future same-workload regression gets its own issue. Notes M2 uses the corrected phase. The initial `/api/v1/notes/?limit=100` returned five 404s (median/p95/max 43.6/51.3/51.3 ms); that was the audit's wrong URI, not Note performance. Those results are retained and excluded from M2. The corrected phase completed eight endpoint profiles, then stopped on an Admin burst `get: socket hang up` before the Note-detail probe. #705 owns diagnosis. It is neither a timeout sample nor evidence of a proved server crash. No kernel OOM entry was found; instantaneous health/process exit were not captured. The original phase's Admin M9 is valid and remains separate. ### Retained Tab diagnostics The production UI run saved eight samples before `requested Tab did not become selected`. The retained condition counts are cold Calendar→Photos n=3, cold Photos→Calendar n=3, and each warm direction n=1. No condition has five samples; no acceptance p95 is claimed. The error is retained with the #549/#641 benchmark gap. | Direction/state | n | Calendar DOM median/max ms | Full target paint median/max ms | | --- | ---: | --- | --- | | calendar→photos cold | 3 | not a Calendar target | 1524.5/1568.9 | | photos→calendar cold | 3 | 1390.1/2291.1 | 3561.0/6304.5 | | photos→calendar warm | 1 | 401.1/401.1 | 3423.8/3423.8 | | calendar→photos warm | 1 | not a Calendar target | 573.1/573.1 | New first visits were Calendar 4239.4 ms fully painted and Photos 1789.7 ms (one sample each, not a percentile). Browser-host load during retained switches was 12.5–19.75 (1 min); VM load was 2.38–3.29. Older #549's 11 warm Chromium phone Calendar DOM samples had p95 229.5 ms. Its old First/Full counter race is documented; only target-boundary-safe marks are usable. These results use different source/load/fixture conditions and are not a before/after ratio. ## Mode × rule matrix R1 Index/precompute; R2 bounded keyset/window; R3 header/revision/ETag; R4 optimistic client-ID/receipt/Undo; R5 one User stream/delta; R6 retained snapshot; R7 virtualization/narrow rows/non-await keys; R8 background/count/read priority; R9 production/HDD budgets. | Surface | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Calendar | F | F | F | F | F | F | F | F | F† | | Notes | F | F | F | F | F | F | F | F | F† | | Files | F | F | F | F | F | F | F† | F | F† | | Photos | F | F | F | F | F | F | F | F | F† | | Mail | F | F | F | F | F | F | F | F | F† | | Money | F | F | F | F | F | F | F | F | F† | | Settings | F | F | F | F | F | F | F | F | F† | | Search | F | F | F | F | F | F | F | F | F† | | Admin | F | F | F | F | F | F | F | F | F† | ### Cell evidence The source observation and measured M reference are separate evidence. R9 F† explicitly covers missing cached-open/action/first-usable/blaze and browser/device/large-fixture measurements. Rows below do not imply an API-read latency is the corresponding UI-action latency. #### Calendar — M1 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/calendar/range` | [crates/plugins/calendar/src/feeds/subscriptions.rs:1428](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/calendar/src/feeds/subscriptions.rs#L1428) | Loads Markdown feed definitions on each range. view.rs:151 also expands iCalendar on request. 20,000 external day-item cap does not remove parsing. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#677](https://git.kayg.org/kayg/calternal/issues/677) | | R2 F | `GET /api/v1/calendar/range` | [crates/plugins/calendar/src/view.rs:35](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/calendar/src/view.rs#L35) | A 366-day bound is not a 100-item/byte page. External items can reach 20,000; details and counts share the response. items.rs already supplies a cursor for saved items. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#678](https://git.kayg.org/kayg/calternal/issues/678) | | R3 F | `GET /api/v1/calendar/range` | [apps/web/src/lib/calendar/data.ts:77](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/calendar/data.ts#L77) | 4 range/6 grid/2 year caches are time-keyed, not a revision-keyed body cache with strong conditional reads. Reuse #549. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#669](https://git.kayg.org/kayg/calternal/issues/669) | | R4 F | `POST /api/v1/notes/journal/log/batch` | [apps/web/src/lib/calendar/edits.ts:86](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/calendar/edits.ts#L86) | Pending client IDs exist, but UndoStep at :307 is a client closure. No common durable receipt and inverse contract. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#669](https://git.kayg.org/kayg/calternal/issues/669) | | R5 F | `GET /api/v1/files/events` | [apps/web/src/lib/calendar/data.ts:98](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/calendar/data.ts#L98) | Shares the Files path-hint stream; Notes paths clear all snapshots. No app-wide bounded delta preserves unchanged items. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#669](https://git.kayg.org/kayg/calternal/issues/669) | | R6 F | `GET /api/v1/calendar/range` | [apps/web/src/lib/calendar/data.ts:88](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/calendar/data.ts#L88) | Complete grids retained, but caches bound entries, not bytes/rows, and omit a shared selection/scroll snapshot contract. Existing #549 is partial reuse. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#669](https://git.kayg.org/kayg/calternal/issues/669) | | R7 F | `GET /api/v1/calendar/range` | [packages/ui/src/components/calendar/AgendaList.svelte:1](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/packages/ui/src/components/calendar/AgendaList.svelte#L1) | Agenda defers mounting but keeps visited rows; #549 says no row recycling. Full blaze/frame and narrow-row invariant is not established. Existing #641 owns traversal. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) | | R8 F | `GET /api/v1/calendar/range` | [crates/plugins/calendar/src/view.rs:579](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/calendar/src/view.rs#L579) | Range computes detailed aggregation and awaits thumbnail enqueue before response; read-only pools exist but counts/work are still on first usable path. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#679](https://git.kayg.org/kayg/calternal/issues/679) | | R9 F† | `/today ↔ /photos` | [bench/tab-switch.mjs:932](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L932) | New locked run has ≥5 cold/warm samples; first usable 10k and cached/action/blaze budgets still require distinct markers. Older #549 DOM p95 229.5 ms at phone warm misses 100 ms. Representative M1 read median/p95/max 64.2/518.9/518.9 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Notes — M2 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/notes/{id}` | [crates/plugins/notes/src/lib.rs:3568](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/notes/src/lib.rs#L3568) | General Note GET uses store::read and view parsing on the request. Journal source projection from #549 does not cover this body. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#702](https://git.kayg.org/kayg/calternal/issues/702) | | R2 F | `GET /api/v1/notes` | [crates/plugins/notes/src/lib.rs:3344](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/notes/src/lib.rs#L3344) | Numeric cursor is OFFSET; API cap is 100 but bytes are not capped. noteIndex.svelte.ts:51 drains every page into byId. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#703](https://git.kayg.org/kayg/calternal/issues/703) | | R3 F | `GET /api/v1/notes/{id}` | [apps/web/src/lib/api/notes.ts:18](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/api/notes.ts#L18) | Fetches Note detail each open; JSON etag is not a shared strong HTTP ETag/304 body cache. Existing #639 owns linked-open speed. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#701](https://git.kayg.org/kayg/calternal/issues/701) | | R4 F | `POST /api/v1/notes` | [apps/web/src/routes/notes/+page.svelte:57](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/notes/+page.svelte#L57) | Create awaits response before item publication. Conditional writes exist, but no shared durable client-ID receipt/Undo contract. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#701](https://git.kayg.org/kayg/calternal/issues/701) | | R5 F | `GET /api/v1/notes` | [apps/web/src/lib/notes/noteIndex.svelte.ts:51](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/notes/noteIndex.svelte.ts#L51) | Whole-index reload and Files/Notes invalidation are not the shared changed-since sequence plus capped delta. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#701](https://git.kayg.org/kayg/calternal/issues/701) | | R6 F | `GET /api/v1/notes` | [apps/web/src/routes/notes/+page.svelte:40](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/notes/+page.svelte#L40) | Route owns list/page state; no shared retained rows/cursors/scroll snapshot. General body restoration remains in #639/#641 scope. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#701](https://git.kayg.org/kayg/calternal/issues/701) | | R7 F | `GET /api/v1/notes` | [apps/web/src/lib/components/NoteList.svelte:59](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/components/NoteList.svelte#L59) | NoteList renders every loaded item at :59; 50-row pages accumulate. Narrow recycled rows and zero incomplete blaze frames are not established; use #641/#639. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#639](https://git.kayg.org/kayg/calternal/issues/639) | | R8 F | `GET /api/v1/notes` | [apps/web/src/lib/notes/noteIndex.svelte.ts:51](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/notes/noteIndex.svelte.ts#L51) | Navigator/index metadata drains all title pages. General Note GET parses on request; separate reader pool already exists for list queries. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#704](https://git.kayg.org/kayg/calternal/issues/704) | | R9 F† | `GET /api/v1/notes` | [bench/tab-switch.mjs:620](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L620) | Correct-route list p50/p95/max 52.2/69.6/69.6 ms (n=5) is not cached body/open/action/10k acceptance. Initial wrong trailing-slash 404 was a harness defect; corrected phase stopped before body sampling. Representative M2 read median/p95/max 52.2/69.6/69.6 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Files — M3 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/files/download?inline=true` | [crates/plugins/files/src/lib.rs:3664](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/files/src/lib.rs#L3664) | Indexed folder list already passes the projection-only read path, but Quick Look TextView fetches downloadUrl (viewer.ts:16), streaming Markdown source from disk under the mutation lock. UI text preview must use a committed indexed body; keep original-byte download semantics separate. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#699](https://git.kayg.org/kayg/calternal/issues/699) | | R2 F | `GET /api/v1/files/entries` | [apps/web/src/lib/files/api.ts:19](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/files/api.ts#L19) | Client PAGE=500, server permits 500 (lib.rs:2600); listAll drains all pages. Signed keyset cursor already exists at listing.rs:468; no total byte bound. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#680](https://git.kayg.org/kayg/calternal/issues/680) | | R3 F | `GET /api/v1/files/entries` | [apps/web/src/lib/files/api.ts:41](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/files/api.ts#L41) | 8 first pages and 30 s freshness are not revision/byte-keyed bodies. Download conditional reads at lib.rs:3648 are reuse, not coverage for listings/stat. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#670](https://git.kayg.org/kayg/calternal/issues/670) | | R4 F | `POST /api/v1/files/trash` | [apps/web/src/lib/files/FilesBrowser.svelte:714](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/files/FilesBrowser.svelte#L714) | Row removal follows awaited trash. transfer.ts:138 supplies Undo closures; no durable client-ID receipt/inverse shared with other views. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#670](https://git.kayg.org/kayg/calternal/issues/670) | | R5 F | `GET /api/v1/files/events` | [crates/plugins/files/src/lib.rs:3834](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/files/src/lib.rs#L3834) | SSE sends per-path events (256/replay batch, ten-minute retention); client live.ts:23 refetches rather than pulls app-wide deltas. Durable /changes exists to reuse. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#670](https://git.kayg.org/kayg/calternal/issues/670) | | R6 F | `GET /api/v1/files/entries` | [apps/web/src/lib/files/api.ts:39](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/files/api.ts#L39) | 8 first-page cache entries omit full resident window, cursor chain, selection/scroll and byte limit. Use #549 as seed. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#670](https://git.kayg.org/kayg/calternal/issues/670) | | R7 F† | `GET /api/v1/files/entries` | [apps/web/src/lib/files/FilesBrowser.svelte:241](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/files/FilesBrowser.svelte#L241) | Collection virtualizes DOM, but listAll still accumulates the entire folder. Same-frame preview and two-row highlight proof remain in active #641 blaze-surfaces. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) | | R8 F | `GET /api/v1/files/entries` | [crates/plugins/files/src/listing.rs:259](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/files/src/listing.rs#L259) | COUNT precedes page query; listing.rs:527 reads all outgoing shares for badges. Reader pool exists, but unrelated count/badge work remains before first rows. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#681](https://git.kayg.org/kayg/calternal/issues/681) | | R9 F† | `GET /api/v1/files/entries` | [bench/tab-switch.mjs:621](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L621) | Locked API probe uses a small Home root, not 10k folder first paint or action/cache/blaze. Existing #641/#549 own surface performance verification. Representative M3 read median/p95/max 87.8/1069.4/1069.4 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Photos — M4 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/photos/timeline` | [crates/plugins/photos/src/routes.rs:440](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/photos/src/routes.rs#L440) | Timeline metadata is indexed, but active_roots reads settings from the filesystem on each request (:440) and detail stats the file (:987). Precompute/cache authorized root preferences and detail header projection; image original streams remain file bytes. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#700](https://git.kayg.org/kayg/calternal/issues/700) | | R2 F | `GET /api/v1/photos/timeline` | [crates/plugins/photos/src/index.rs:30](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/photos/src/index.rs#L30) | 90 days × 200 tiles permits 18,000 tiles. timeline/days uses unsigned offset (:653); buckets returns all days (:613). No total 100-row/byte keyset page. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#682](https://git.kayg.org/kayg/calternal/issues/682) | | R3 F | `GET /api/v1/photos/timeline` | [apps/web/src/lib/photos/timeline.svelte.ts:307](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/photos/timeline.svelte.ts#L307) | Timeline filter cache is not a revision-keyed body cache; no strong conditional timeline response. Full-image predecode work belongs to active #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#671](https://git.kayg.org/kayg/calternal/issues/671) | | R4 F | `PUT /api/v1/tags/items` | [apps/web/src/lib/photos/PhotosView.svelte:611](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/photos/PhotosView.svelte#L611) | Per-item sidecar writes are sequential; no common synchronous mutation/client-ID receipt/Undo contract across Files and Photos. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#671](https://git.kayg.org/kayg/calternal/issues/671) | | R5 F | `GET /api/v1/photos/timeline/buckets` | [apps/web/src/lib/photos/PhotosView.svelte:742](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/photos/PhotosView.svelte#L742) | Refresh timers at 900 and 3500 ms rebuild buckets. No shared delta merges with stable unchanged object identity. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#671](https://git.kayg.org/kayg/calternal/issues/671) | | R6 F | `GET /api/v1/photos/timeline` | [apps/web/src/lib/photos/timeline.svelte.ts:307](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/photos/timeline.svelte.ts#L307) | Cached filter stores retain data, but loaded Map (:48) has no shared row/byte-bounded view snapshot with selection/scroll. Decoded-image LRU belongs to #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#671](https://git.kayg.org/kayg/calternal/issues/671) | | R7 F | `GET /api/v1/photos/items/{id}` | [apps/web/src/lib/photos/PhotoViewer.svelte:229](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/photos/PhotoViewer.svelte#L229) | Grid/viewer already use shared display primitives. The #641 source trace found collection-wide URL/date mapping per selection; fixes remain on its active branch. Zero-incomplete traversal not proved. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) | | R8 F | `GET /api/v1/photos/timeline/buckets` | [crates/plugins/photos/src/routes.rs:629](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/photos/src/routes.rs#L629) | All day counts are fetched before first timeline use. Ingest batches 32/200 (index.rs:430/:519) and uses workers, but foreground counts remain part of startup. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#683](https://git.kayg.org/kayg/calternal/issues/683) | | R9 F† | `/photos ↔ /today` | [bench/tab-switch.mjs:932](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L932) | Locked Tab samples use 100 Photos. First usable 10k/50k and cache/action/blaze are not covered by that smoke; reuse active #641. Representative M4 read median/p95/max 46.0/48.2/48.2 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Mail — M5 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/mail/messages/{id}/attachments/{section_id}` | [crates/plugins/mail/src/routes.rs:1479](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/mail/src/routes.rs#L1479) | Attachment open connects to IMAP in the handler. message_detail (:1404) extracts links and can sanitize raw HTML on each read. Inbox headers already use the Index. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#684](https://git.kayg.org/kayg/calternal/issues/684) | | R2 F | `GET /api/v1/mail/inbox/messages` | [crates/plugins/mail/src/routes.rs:1128](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/mail/src/routes.rs#L1128) | Messages capped at 100 but cursors are unsigned before_received_ms/before_id. No response byte cap; thread attachment query cache/store.rs:1484 permits 500 rows. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#685](https://git.kayg.org/kayg/calternal/issues/685) | | R3 F | `GET /api/v1/mail/messages/{id}` | [apps/web/src/lib/mail/MailView.svelte:384](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L384) | Detail open awaits a new fetch; no common revision cache/strong conditional body contract. #640 already owns reading layouts and neighbour prefetch. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#672](https://git.kayg.org/kayg/calternal/issues/672) | | R4 F | `POST /api/v1/mail/messages/{id}/read-state` | [apps/web/src/lib/mail/MailView.svelte:238](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L238) | Updates rows after awaited POST, not one synchronous commit. Existing provider sync queue is not a User-visible client-ID mutation receipt and durable Undo. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#672](https://git.kayg.org/kayg/calternal/issues/672) | | R5 F | `GET /api/v1/mail/inbox/messages` | [apps/web/src/lib/mail/MailView.svelte:227](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L227) | 30,000 ms inbox poll; no app-wide changed-since stream/delta. New-mail notice preserves current reading position and must remain. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#672](https://git.kayg.org/kayg/calternal/issues/672) | | R6 F | `GET /api/v1/mail/inbox/messages` | [apps/web/src/lib/mail/MailView.svelte:330](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L330) | Reload obtains accounts, folders, list and detail. #640/#641 have newer folder/body caches; integrate those before adapting shared snapshots. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#672](https://git.kayg.org/kayg/calternal/issues/672) | | R7 F | `GET /api/v1/mail/inbox/messages` | [apps/web/src/lib/mail/MailView.svelte:600](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L600) | Base list renders every loaded thread row. #640 owns virtualized reader layouts; #641 measures held-key completeness. Do not duplicate active work. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#640](https://git.kayg.org/kayg/calternal/issues/640) | | R8 F | `GET /api/v1/mail/accounts` | [apps/web/src/lib/mail/MailView.svelte:330](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/mail/MailView.svelte#L330) | Accounts await folders before message first paint. Precomputed folder counts and reader_pool exist; defer non-selected folder/count work and verify reads during backfill. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#686](https://git.kayg.org/kayg/calternal/issues/686) | | R9 F† | `GET /api/v1/mail/inbox/messages` | [bench/tab-switch.mjs:88](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L88) | Audit fixture Mail is empty (0 rows, 24 bytes). No large-mailbox/body latency or blaze proof; #640/#641/#626 own relevant active runs. Representative M5 read median/p95/max 41.1/48.0/48.0 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#640](https://git.kayg.org/kayg/calternal/issues/640) | #### Money — M6 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/money/budgets/{id}/accounts` | [crates/plugins/money/src/store.rs:432](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/money/src/store.rs#L432) | Changed source is read and parsed under the User lock on a request. Kernel-identity parse cache is reuse, not an indexed committed projection. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#687](https://git.kayg.org/kayg/calternal/issues/687) | | R2 F | `GET /api/v1/money/budgets/{id}/transactions` | [crates/plugins/money/src/routes.rs:916](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/money/src/routes.rs#L916) | Returns all matching transactions; no cursor/row/byte bound. Budgets (:338) and accounts (:612) also return complete sets. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#688](https://git.kayg.org/kayg/calternal/issues/688) | | R3 F | `GET /api/v1/money/budgets/{id}/accounts` | [apps/web/src/lib/money/api.ts:18](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/money/api.ts#L18) | no-store reads; one last account answer retained, not revision-keyed register/body cache with conditional reads. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#673](https://git.kayg.org/kayg/calternal/issues/673) | | R4 F | `PUT /api/v1/money/budgets/{id}/transactions/{transaction_id}/cleared` | [apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:133](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte#L133) | Waits for state write then full load. No durable receipt/client-ID inverse and synchronous cross-view selection update. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#673](https://git.kayg.org/kayg/calternal/issues/673) | | R5 F | `GET /api/v1/money/budgets/{id}/accounts` | [apps/web/src/lib/money/store.svelte.ts:87](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/money/store.svelte.ts#L87) | refresh re-reads account answers after local write; no cross-Installation changed-since stream/delta for register or Budget. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#673](https://git.kayg.org/kayg/calternal/issues/673) | | R6 F | `GET /api/v1/money/budgets/{id}/accounts` | [apps/web/src/lib/money/store.svelte.ts:57](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/money/store.svelte.ts#L57) | Budget switch sets accounts=null; one account set retained, no shared register/cursor/scroll LRU. Active #641 bounded neighbour work must be reused. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#673](https://git.kayg.org/kayg/calternal/issues/673) | | R7 F | `GET /api/v1/money/budgets/{id}/transactions` | [apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:291](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte#L291) | Register each block renders all transactions; active #641 owns account-arrow and prefetch work but does not establish a globally bounded register. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) | | R8 F | `GET /api/v1/money/budgets/{id}/accounts` | [crates/plugins/money/src/routes.rs:612](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/money/src/routes.rs#L612) | Request loads ledger and replays totals rather than reads precomputed committed answers. Shared User read lock can wait for writes; no separate Index reader projection. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#689](https://git.kayg.org/kayg/calternal/issues/689) | | R9 F† | `GET /api/v1/money/budgets/{id}/accounts` | [apps/web/src/lib/money/store.svelte.ts:6](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/money/store.svelte.ts#L6) | Five locked API samples cover a small fixture only. Active #641 reports incomplete account steps; no first usable 10k register or accepted/durable action proof. Representative M6 read median/p95/max 44.7/50.8/50.8 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Settings — M7 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/appearance` | [crates/calternal-server/src/appearance.rs:739](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-server/src/appearance.rs#L739) | Reads and projects settings.json on a UI read. Auth/security reads use SQLite, but not all Settings cards use a precomputed Index projection. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#690](https://git.kayg.org/kayg/calternal/issues/690) | | R2 F | `GET /api/v1/auth/sessions` | [crates/calternal-auth/src/store.rs:2043](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-auth/src/store.rs#L2043) | Sessions returns a complete Vec with no cursor/byte cap. Fixed Settings navigation labels are bounded; sessions/App Passwords lists can grow. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#691](https://git.kayg.org/kayg/calternal/issues/691) | | R3 F | `GET /api/v1/auth/me/security` | [apps/web/src/routes/settings/api.svelte.ts:74](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/api.svelte.ts#L74) | Resource load publishes a fresh value without revision/ETag cache. #642 adds safe per-User card snapshots on its branch; reuse before common adoption. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#674](https://git.kayg.org/kayg/calternal/issues/674) | | R4 F | `PUT /api/v1/appearance` | [apps/web/src/routes/settings/api.svelte.ts:23](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/api.svelte.ts#L23) | withStepUp awaits a definitive action. Safe preferences lack shared mutation receipt/Undo; security changes must keep step-up and must not optimistically grant authority. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#674](https://git.kayg.org/kayg/calternal/issues/674) | | R5 F | `GET /api/v1/appearance` | [apps/web/src/lib/stores/settings-store.ts:1](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/stores/settings-store.ts#L1) | Local per-User preference state exists, but no shared stream/delta carries preference changes across Installations and retained views. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#674](https://git.kayg.org/kayg/calternal/issues/674) | | R6 F | `/settings/account` | [apps/web/src/routes/settings/[...path]/+page.svelte:3](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/[...path]/+page.svelte#L3) | Overlay owns visited section lifecycle; base Resource loads per mount. #642 retained safe resources and mounted sections are partial reuse, not shared byte LRU. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#674](https://git.kayg.org/kayg/calternal/issues/674) | | R7 F | `GET /api/v1/auth/sessions` | [apps/web/src/routes/settings/account/SessionsGroup.svelte:88](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/account/SessionsGroup.svelte#L88) | Sessions renders every loaded entry. Static rail itself does not need virtualization. #641 owns rail blaze navigation and latest-intent frame sampler. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#642](https://git.kayg.org/kayg/calternal/issues/642) | | R8 F | `GET /api/v1/auth/me/security` | [crates/calternal-auth/src/store.rs:2043](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-auth/src/store.rs#L2043) | Auth reads share store.pool instead of a separate read-only pool; base Settings cards start their data reads on mount. #642 scheduling work is active reuse. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#692](https://git.kayg.org/kayg/calternal/issues/692) | | R9 F† | `/settings/account` | [bench/settings-shortcut.mjs:1](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/settings-shortcut.mjs#L1) | Five locked security-read samples are not full-card opening latency. #642 final local warm content 377.4 ms and cold 1103.9 ms are not locked HDD acceptance. Representative M7 read median/p95/max 482.2/1266.5/1266.5 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#642](https://git.kayg.org/kayg/calternal/issues/642) | #### Search — M8 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/search` | [crates/plugins/calendar/src/search.rs:76](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/plugins/calendar/src/search.rs#L76) | Calendar provider parses legacy iCalendar tags when tags_json is absent; Search Log preview calls loadRange (SearchPreview.svelte:142), which loads Markdown feed definitions. Indexed keyword lookup alone is not full preview coverage. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#693](https://git.kayg.org/kayg/calternal/issues/693) | | R2 F | `GET /api/v1/search` | [crates/calternal-server/src/main.rs:1023](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-server/src/main.rs#L1023) | No final global cap; up to 200 hits per provider, no signed result-window cursor or response byte cap. Bounded plugin lists do not bound the combined response. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#694](https://git.kayg.org/kayg/calternal/issues/694) | | R3 F | `GET /api/v1/search` | [apps/web/src/lib/search/SearchPreview.svelte:173](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/SearchPreview.svelte#L173) | 80-entry cache keyed only by target.id, with no revision/byte/User key or session-clear handler in this module. Needs #555 lifecycle and #665. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#675](https://git.kayg.org/kayg/calternal/issues/675) | | R4 F | `POST/PATCH/DELETE /api/v1/search/saved` | [apps/web/src/lib/search/saved.svelte.ts:125](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/saved.svelte.ts#L125) | Saved-search create/update/remove await the API before changing items (:126/:133/:140). No client-ID durable receipt/Undo or synchronous retained-query update. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#675](https://git.kayg.org/kayg/calternal/issues/675) | | R5 F | `GET /api/v1/search` | [apps/web/src/lib/search/window.svelte.ts:408](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/window.svelte.ts#L408) | Queries pull full answers and saved counts are refreshed separately. No shared delta applies changed IDs/revisions while keeping unchanged hits by identity. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#675](https://git.kayg.org/kayg/calternal/issues/675) | | R6 F | `/search?q=…` | [apps/web/src/lib/search/window.svelte.ts:1](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/window.svelte.ts#L1) | Query state is retained in the window, but no shared byte/row LRU for query/facet/result cursor, preview and scroll. Existing 80-preview cache is partial reuse. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#675](https://git.kayg.org/kayg/calternal/issues/675) | | R7 F | `GET /api/v1/search` | [apps/web/src/lib/search/SearchPreview.svelte:180](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/SearchPreview.svelte#L180) | Preview debounces each uncached target by 70 ms, conflicting with every-step 15/33 ms blaze. Expanded result list is already virtualized above 60 (:182 in search-dialog). Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) | | R8 F | `GET /api/v1/search` | [apps/web/src/lib/search/server.ts:145](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/lib/search/server.ts#L145) | Keyword first then hybrid after 80 ms is reuse; base Notes metadata loader still drains corpus and providers perform request-time detail work. Background read priority not established for all providers. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#695](https://git.kayg.org/kayg/calternal/issues/695) | | R9 F† | `GET /api/v1/search` | [bench/search-group-rows.mjs:1](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/search-group-rows.mjs#L1) | Five locked keyword API reads are not 100k hybrid results or every-frame preview completeness. #641 owns shared blaze targets; add Search adapter after its harness lands. Representative M8 read median/p95/max 49.3/55.3/55.3 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) | #### Admin — M9 | Rule / result | Endpoint | Source at audit base | Evidence and numeric bound | Owner | | --- | --- | --- | --- | --- | | R1 F | `GET /api/v1/admin/config/toml` | [crates/calternal-server/src/wire.rs:4018](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-server/src/wire.rs#L4018) | Raw TOML read comes from filesystem in request. Parsed config GET at :3739 already uses live in-memory state; do not persist raw config in browser caches. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#696](https://git.kayg.org/kayg/calternal/issues/696) | | R2 F | `GET /api/v1/auth/users` | [crates/calternal-auth/src/store.rs:1611](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-auth/src/store.rs#L1611) | Unbounded SELECT * and fetch_all for Users. No keyset cursor or byte cap; Jobs already has bounded list support to reuse. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#697](https://git.kayg.org/kayg/calternal/issues/697) | | R3 F | `GET /api/v1/auth/users` | [apps/web/src/routes/settings/admin/UsersGroup.svelte:34](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/admin/UsersGroup.svelte#L34) | Resource fetch has no shared revision/conditional-read contract. Cache only public summary fields after authorization; raw TOML and secrets stay out of persistent caches. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#665](https://git.kayg.org/kayg/calternal/issues/665) → [#676](https://git.kayg.org/kayg/calternal/issues/676) | | R4 F | `PATCH /api/v1/auth/users/{id}/role` | [apps/web/src/routes/settings/admin/UsersGroup.svelte:69](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/admin/UsersGroup.svelte#L69) | State-changing operations await step-up and refresh. Shared receipts are absent; safe pending UI must not grant authority or claim purge/revoke is undone. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#667](https://git.kayg.org/kayg/calternal/issues/667) → [#676](https://git.kayg.org/kayg/calternal/issues/676) | | R5 F | `GET /api/v1/auth/users` | [apps/web/src/routes/settings/admin/UsersGroup.svelte:34](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/admin/UsersGroup.svelte#L34) | No cross-Installation sequence/delta for User summaries, plugin switches or Jobs; enforce Admin role again at each delta read and revoke retained state. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#668](https://git.kayg.org/kayg/calternal/issues/668) → [#676](https://git.kayg.org/kayg/calternal/issues/676) | | R6 F | `/settings/admin/users` | [apps/web/src/routes/settings/api.svelte.ts:74](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/api.svelte.ts#L74) | Cards own Resource state rather than shared complete view snapshots. #642 Admin authorization and memory-only raw TOML lifecycle must remain. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#666](https://git.kayg.org/kayg/calternal/issues/666) → [#676](https://git.kayg.org/kayg/calternal/issues/676) | | R7 F | `GET /api/v1/auth/users` | [apps/web/src/routes/settings/admin/UsersGroup.svelte:136](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/apps/web/src/routes/settings/admin/UsersGroup.svelte#L136) | Renders all User rows. No narrow virtualization for large Instances; rail traversal belongs to #641/#642 and must not be duplicated. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#642](https://git.kayg.org/kayg/calternal/issues/642) | | R8 F | `GET /api/v1/auth/users` | [crates/calternal-auth/src/store.rs:1611](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/crates/calternal-auth/src/store.rs#L1611) | Auth queries use same pool as writes and return all users before paint. Core Db reader_pool at db.rs:69 is existing shared infrastructure to use. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#698](https://git.kayg.org/kayg/calternal/issues/698) | | R9 F† | `GET /api/v1/auth/users` | [bench/tab-switch.mjs:88](https://git.kayg.org/kayg/calternal/src/commit/c4a61e8cf090170f35b1bed3350d9de20c83ecd5/bench/tab-switch.mjs#L88) | Five locked reads cover one synthetic User only. No large Users/Jobs list paint, action/Undo or blaze acceptance; #641/#642 supply harness and overlay work. Representative M9 read median/p95/max 140.4/929.4/929.4 ms. | [#549](https://git.kayg.org/kayg/calternal/issues/549) / [#641](https://git.kayg.org/kayg/calternal/issues/641) / [#642](https://git.kayg.org/kayg/calternal/issues/642) | ## Filed work and ownership **One shared owner per primitive:** #665 revision cache + ETag/304; #666 view-snapshot LRU; #667 optimistic mutation/client IDs/durable inverse receipts; #668 per-User stream + capped deltas. Each has a self-contained context, source and measurement evidence, expected behavior, regression tests and performance acceptance. Each surface has one shared-contract adoption issue, plus a rule-specific issue for each remaining R1/R2/R8 gap. Shared R3–R6 fixes are grouped under one primitive owner rather than multiplied per surface. R7/R9 retain the existing #641/#549 and surface speed owners; new adoption tests use their harness and integrate their branch results. No duplicate blaze/layout/opening jobs were filed. | Surface | Adopt R3–R6 | R1 projection/read path | R2 paging/window | R8 first-paint/read priority | | --- | --- | --- | --- | --- | | Calendar | [#669](https://git.kayg.org/kayg/calternal/issues/669) | [#677](https://git.kayg.org/kayg/calternal/issues/677) | [#678](https://git.kayg.org/kayg/calternal/issues/678) | [#679](https://git.kayg.org/kayg/calternal/issues/679) | | Notes | [#701](https://git.kayg.org/kayg/calternal/issues/701) | [#702](https://git.kayg.org/kayg/calternal/issues/702) | [#703](https://git.kayg.org/kayg/calternal/issues/703) | [#704](https://git.kayg.org/kayg/calternal/issues/704) | | Files | [#670](https://git.kayg.org/kayg/calternal/issues/670) | [#699](https://git.kayg.org/kayg/calternal/issues/699) | [#680](https://git.kayg.org/kayg/calternal/issues/680) | [#681](https://git.kayg.org/kayg/calternal/issues/681) | | Photos | [#671](https://git.kayg.org/kayg/calternal/issues/671) | [#700](https://git.kayg.org/kayg/calternal/issues/700) | [#682](https://git.kayg.org/kayg/calternal/issues/682) | [#683](https://git.kayg.org/kayg/calternal/issues/683) | | Mail | [#672](https://git.kayg.org/kayg/calternal/issues/672) | [#684](https://git.kayg.org/kayg/calternal/issues/684) | [#685](https://git.kayg.org/kayg/calternal/issues/685) | [#686](https://git.kayg.org/kayg/calternal/issues/686) | | Money | [#673](https://git.kayg.org/kayg/calternal/issues/673) | [#687](https://git.kayg.org/kayg/calternal/issues/687) | [#688](https://git.kayg.org/kayg/calternal/issues/688) | [#689](https://git.kayg.org/kayg/calternal/issues/689) | | Settings | [#674](https://git.kayg.org/kayg/calternal/issues/674) | [#690](https://git.kayg.org/kayg/calternal/issues/690) | [#691](https://git.kayg.org/kayg/calternal/issues/691) | [#692](https://git.kayg.org/kayg/calternal/issues/692) | | Search | [#675](https://git.kayg.org/kayg/calternal/issues/675) | [#693](https://git.kayg.org/kayg/calternal/issues/693) | [#694](https://git.kayg.org/kayg/calternal/issues/694) | [#695](https://git.kayg.org/kayg/calternal/issues/695) | | Admin | [#676](https://git.kayg.org/kayg/calternal/issues/676) | [#696](https://git.kayg.org/kayg/calternal/issues/696) | [#697](https://git.kayg.org/kayg/calternal/issues/697) | [#698](https://git.kayg.org/kayg/calternal/issues/698) | #705 is the separate non-SLOW transport triage. Total: **41 new issues** = four shared owners + nine adopters + 27 rule-specific tasks + one transport investigation. None was closed. ### Reuse and active work - #555 `userStorage` owns User keying/session-end cleanup. Do not add another browser persistence module. - #549 route cache, complete Calendar grid/Agenda snapshots, Journal committed-source projection, generation guards and target-paint harness. Retain its Notes/Files correctness fixes and #627 destination-identity work. - Files signed keyset pages, durable change feed, #452 User stream isolation, existing strong download/thumbnail ETags and Undo journals. Files R2 #680 owns any minimum public signed-cursor/bounds extraction. Other modes adopt it. - Core `Db::reader_pool` already exists (`crates/calternal-db/src/db.rs:69`). Auth currently uses a separate store pool for reads/writes; adopt, do not duplicate core read machinery. Admin R2 #697 owns the shared Auth paging addition; Settings #691 adds its User-scoped adapters. - #641 comments identify `job/blaze-settings` (harness and Settings), `job/blaze-surfaces` (Files/Photos/Money/Tabs), and `job/maillayouts` (Mail). Latest Settings closeout is `bf3ad5f29…`; Mail #640 closeout `f9f360e68…`; blaze-surfaces follow-up comments still trace Photos/Money/Tabs. These branches are reuse/dependencies, not claims that their code is on this audit base. - #642 remains Settings opening/resource/overlay owner; #640 remains Mail layouts/neighbor body cache owner. #639 owns linked Note opening. #613/#614/#626 own provider sync and UID work. #661/#634 and Notes restart issues own Note-write correctness. - Generic file-text preview belongs to Files #699; Note/collaboration body publication belongs to Notes #702. Photos #700 adopts Settings #690 preference projection. Search #695 adopts Notes #704 metadata bounds rather than changing the same loader in two jobs. ## Gates (verbatim output) `cargo fmt --check`: exit 0, no output. `git diff --check`: exit 0, no output. No Rust crate, route, contract or web product code changed; per-crate clippy/test and API adversarial merge gates are not applicable to these documentation-only commits. The read-only measurement bursts were diagnostic probes, not a claim of a full adversarial round. `bun run check` (exit 0): ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/instant-663/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` (exit 0; summary quoted verbatim): ```text Test Files 153 passed (153) Tests 1053 passed (1053) Start at 07:34:21 Duration 158.13s (transform 52%, environment 19%, import 13%, tests 12%, setup 4%) ``` The existing jsdom `Window's scrollTo()` messages remain; no assertion or fixture was changed. `bun run build` (exit 0; final output excerpt): ```text ✓ built in 56.00s Run npm run preview to preview your production build locally. > Using @sveltejs/adapter-static Wrote site to "build" ✔ done ``` `cargo clean` (exit 0): ```text Removed 1 file, 356B total ``` Generated `apps/web/build` and `apps/web/.svelte-kit` were deleted. Review data stay under ignored `artifacts/instant-663/`. No screenshots, logs or data were committed. ## Decisions - Treat a rule as a full-interaction conjunction. A partial indexed list/cache is reuse, not a complete Tab Pass. Use F† for a missing acceptance measurement instead of inventing a result. This audit classification is not a new product behavior. - Use the required shared release binary as measured, record its exact older source/hash, and keep the newer static source audit separate. Do not replace the shared baseline or claim a controlled ratio. - Group shared R3–R6 into four owner issues and one adopter per surface; keep R1/R2/R8 as rule-specific jobs and R7/R9 with the active benchmark/surface owners. This prevents duplicate primitive owners. - Original binary downloads keep their byte-stream semantics. Interactive Markdown text previews need an indexed source; the Files/Notes issues divide that implementation. No new storage format, byte cap value, cache TTL, receipt retention or migration number is chosen in this audit. Those implementation choices must be recorded by their owner issue. ## UX gaps closed / left Closed: no product UX change in this audit-only job. The plan now assigns cached restoration, same-frame cross-view mutations, durable Undo, Copy link/focus/touch/screen-reader tests and real empty/error/offline states to the appropriate owners. Left: the actual product gaps in the 81 cells await those jobs. No complete 10k first-usable/cached-open/action/blaze matrix, full-size mailbox/register/folder fixture, all widths/themes/engines, or Note-detail timing was measured here. The interrupted UI and Admin transport cases remain explicit; no spinner/cache/action claim is called proved from an API p95. ## Audit artifacts Artifacts are ignored and remain local for review; the measurements, matrix and issue mapping above are the durable issue record. Files: - `artifacts/instant-663/audit-hdd.mjs` SHA-256 `fd38d44c13c5481ef2b1e68a519c8b3620a1b112a4812c8766f8ca20f6fa79f9` - `artifacts/instant-663/audit-reads.mjs` SHA-256 `b998f6551c5abe684ac59d7b98be55094855b8a93e8bb156ddee9960bdf3f9ba` - `artifacts/instant-663/api-hdd.json` SHA-256 `b129bc494dab4171f6882911ed86bb625b0e5234d4b5da516f533662b4136ac1` - `artifacts/instant-663/api-hdd-corrected-partial.json` SHA-256 `e7983fe87cbf1d9ad8620ddda7875c4a99f49f981f6f9947cc10d7f887daed35` - `artifacts/instant-663/production-hdd.json` SHA-256 `38c2011b2820c3da281dcf490183d44da4eb4717b0973838491ea3509a1fa3ee` - `artifacts/instant-663/cells.json` SHA-256 `6b1fe7347a048338615e4b393cabd8e8ee652e58c5e94e203772976afdd24729` The audit source was derived from `bench/tab-switch.mjs`, adding the listed five-read/burst endpoint probes; `audit-reads.mjs` corrects the Notes URI and stops after server probes. The complete sampled numbers and stopped-phase reasons are retained above.
Author
Owner

Starting the server hot-path audit on job/perf-arch-server, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Read CLAUDE.md, CONTEXT.md and DESIGN. This base has no DESIGN §58; I will use #663 rules as the bar. Read-mostly work; findings will go in audit-findings.md and duplicate-checked follow-up issues. No product edits, pushes or deploys. Pending merge-round-7 branches will be checked where they change audited paths.

Starting the server hot-path audit on `job/perf-arch-server`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Read CLAUDE.md, CONTEXT.md and DESIGN. This base has no DESIGN §58; I will use #663 rules as the bar. Read-mostly work; findings will go in audit-findings.md and duplicate-checked follow-up issues. No product edits, pushes or deploys. Pending merge-round-7 branches will be checked where they change audited paths.
Author
Owner

Starting the read-mostly web runtime audit on job/perf-arch-client. Base: c4a61e8cf0 (origin/dev). I will check merge-round-7a and the paused round-7b branches where relevant, record file/line evidence in audit-findings.md, search for duplicates, and file focused findings. No product edits, push or deploy. DESIGN section 58 is absent on this base; issue #663 supplies the performance rules.

Starting the read-mostly web runtime audit on job/perf-arch-client. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). I will check merge-round-7a and the paused round-7b branches where relevant, record file/line evidence in audit-findings.md, search for duplicates, and file focused findings. No product edits, push or deploy. DESIGN section 58 is absent on this base; issue #663 supplies the performance rules.
Author
Owner

Memory and CPU audit started on job/perf-arch-memory; base origin/dev c4a61e8cf0. Read-mostly scope: resident state, queues, idle work, large Home and mailbox operations, and connection costs. Findings will be committed in audit-findings.md and checked against round 7a and pending round 7b code. No product edits or deployment.

Memory and CPU audit started on job/perf-arch-memory; base origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Read-mostly scope: resident state, queues, idle work, large Home and mailbox operations, and connection costs. Findings will be committed in audit-findings.md and checked against round 7a and pending round 7b code. No product edits or deployment.
Author
Owner

Starting the requested sec-auth defensive audit on job/sec-auth, base c4a61e8cf0 (origin/dev). No product edits. Scope mismatch: this issue is the performance architecture issue and DESIGN ends at §57; §58 is absent. I will use the auth rules in §7 and §21 and record this limitation.

Starting the requested sec-auth defensive audit on job/sec-auth, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). No product edits. Scope mismatch: this issue is the performance architecture issue and DESIGN ends at §57; §58 is absent. I will use the auth rules in §7 and §21 and record this limitation.
Author
Owner

Started defensive admin/configuration/deployment audit on job/sec-admin-deploy, base c4a61e8cf0 (origin/dev). Job scope differs from this performance issue; current DESIGN ends at section 57, with no section 58. I will follow the explicit defensive job scope, record this limitation, and make no product edits or deployments.

Started defensive admin/configuration/deployment audit on job/sec-admin-deploy, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Job scope differs from this performance issue; current DESIGN ends at section 57, with no section 58. I will follow the explicit defensive job scope, record this limitation, and make no product edits or deployments.
Author
Owner

Started disk IO and startup audit for #663.

Branch: job/perf-arch-io. Base and audited origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Scope: read-only product review; write audit-findings.md and file confirmed findings after duplicate search. No product edits, push, deploy or feature benchmark. Check merge-round-7a and the queued round-7b branches for changed IO paths.

DESIGN on origin/dev ends at §57. On merge-round-7a, §58 is agent discovery (#630), not performance (#663). Use the explicit rules in #663 as the performance bar and record this section mismatch in the report.

Started disk IO and startup audit for #663. Branch: `job/perf-arch-io`. Base and audited origin/dev: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Scope: read-only product review; write `audit-findings.md` and file confirmed findings after duplicate search. No product edits, push, deploy or feature benchmark. Check merge-round-7a and the queued round-7b branches for changed IO paths. DESIGN on origin/dev ends at §57. On merge-round-7a, §58 is agent discovery (#630), not performance (#663). Use the explicit rules in #663 as the performance bar and record this section mismatch in the report.
Author
Owner

Started sec-browser defensive audit on job/sec-browser. Base and origin/dev: c4a61e8cf0. Scope: read-only browser security review; audit-findings.md only. Issue #663 is currently the performance umbrella, and DESIGN on origin/dev ends at §57; I will use the direct security brief plus §§21, 45, 57 and inspect merge-round-7a for §58. No product edits, push or deploy.

Started sec-browser defensive audit on job/sec-browser. Base and origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Scope: read-only browser security review; audit-findings.md only. Issue #663 is currently the performance umbrella, and DESIGN on origin/dev ends at §57; I will use the direct security brief plus §§21, 45, 57 and inspect merge-round-7a for §58. No product edits, push or deploy.
Author
Owner

Starting the sec-supplychain job on branch job/sec-supplychain, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Read CLAUDE.md, CONTEXT.md and DESIGN.md. Scope: read-only dependency, licence, vendor, reproducibility and model-download audit; report-only commits, no product edits. The supplied brief calls this a security audit, but #663 currently describes performance and DESIGN ends at §57. I follow the explicit job brief and use the security and licence rules in CLAUDE.md and DESIGN §§2, 3, 12, 28, 36. Findings will go in audit-findings.md and separate issues after duplicate search.

Starting the sec-supplychain job on branch `job/sec-supplychain`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (origin/dev). Read CLAUDE.md, CONTEXT.md and DESIGN.md. Scope: read-only dependency, licence, vendor, reproducibility and model-download audit; report-only commits, no product edits. The supplied brief calls this a security audit, but #663 currently describes performance and DESIGN ends at §57. I follow the explicit job brief and use the security and licence rules in CLAUDE.md and DESIGN §§2, 3, 12, 28, 36. Findings will go in audit-findings.md and separate issues after duplicate search.
Author
Owner

Started sync and live-update architecture audit for #663. Branch: job/perf-arch-sync. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Scope: Mail IMAP sync, CalDAV/CardDAV, WebDAV, Notes collaboration rooms, #668 change stream, SSE notifications. Read-only code review; findings and focused evidence only. No product edits, pushes or deploys. I will check round-7a and queued round-7b changes before filing findings.

Started sync and live-update architecture audit for #663. Branch: `job/perf-arch-sync`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Scope: Mail IMAP sync, CalDAV/CardDAV, WebDAV, Notes collaboration rooms, #668 change stream, SSE notifications. Read-only code review; findings and focused evidence only. No product edits, pushes or deploys. I will check round-7a and queued round-7b changes before filing findings.
Author
Owner

Started perf-arch-db (#663). Branch: job/perf-arch-db. Base: origin/dev c4a61e8cf0. Read-mostly schema/query audit; no product changes. The checked-out DESIGN ends at §57; I use the rules in #663 until §58 is present. I will check merge-round-7a and queued branch differences, record query plans, and search for duplicate findings before filing.

Started perf-arch-db (#663). Branch: job/perf-arch-db. Base: origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Read-mostly schema/query audit; no product changes. The checked-out DESIGN ends at §57; I use the rules in #663 until §58 is present. I will check merge-round-7a and queued branch differences, record query plans, and search for duplicate findings before filing.
Author
Owner

Filesystem audit started on job/sec-fs, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Scope: User input to filesystem boundaries, uploads, move/rename, Trash/restore, media, dedup, quotas and temporary files. No product changes, push or deployment.

Reference mismatch: #663 currently describes performance, and DESIGN on this base ends at §57 (no §58). I follow the explicit sec-fs audit brief and the existing security rules in DESIGN §2, §5, §22, §26 and §39. Findings will be recorded in audit-findings.md, with duplicate checks before filing issues.

Filesystem audit started on `job/sec-fs`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Scope: User input to filesystem boundaries, uploads, move/rename, Trash/restore, media, dedup, quotas and temporary files. No product changes, push or deployment. Reference mismatch: #663 currently describes performance, and DESIGN on this base ends at §57 (no §58). I follow the explicit sec-fs audit brief and the existing security rules in DESIGN §2, §5, §22, §26 and §39. Findings will be recorded in `audit-findings.md`, with duplicate checks before filing issues.
Author
Owner

Security audit start: branch job/sec-mcp-scopes, base origin/dev and HEAD c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

The job brief names #663 as the MCP/API/CLI scope audit. The live issue is a performance audit. The current dev design ends at §57; merge-round-7a adds §58 for agent discovery (#630), not #663. I will perform the requested read-mostly security audit, keep these differences explicit, and use DESIGN §§21, 41, 48 and 55 as the available security rules. No product edits, push or deploy.

Security audit start: branch `job/sec-mcp-scopes`, base `origin/dev` and HEAD `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. The job brief names #663 as the MCP/API/CLI scope audit. The live issue is a performance audit. The current dev design ends at §57; merge-round-7a adds §58 for agent discovery (#630), not #663. I will perform the requested read-mostly security audit, keep these differences explicit, and use DESIGN §§21, 41, 48 and 55 as the available security rules. No product edits, push or deploy.
Author
Owner

Protocol audit started on job/sec-protocols. Base: c4a61e8cf0 (origin/dev). Scope: DAV, Notes IMAP, queued mail proxy, SMTP validation stub, MCP HTTP and SSE. Read-only review; no product edits. The job references #663 and DESIGN §58, but #663 currently describes performance and this base ends at §57. I will use the explicit protocol audit brief and the security rules in CLAUDE.md and DESIGN §§21, 45, 51, 53–55. Findings will include source evidence, limits of validation, duplicate checks and proposed regression coverage. No secrets or hostile payloads will be published.

Protocol audit started on job/sec-protocols. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Scope: DAV, Notes IMAP, queued mail proxy, SMTP validation stub, MCP HTTP and SSE. Read-only review; no product edits. The job references #663 and DESIGN §58, but #663 currently describes performance and this base ends at §57. I will use the explicit protocol audit brief and the security rules in CLAUDE.md and DESIGN §§21, 45, 51, 53–55. Findings will include source evidence, limits of validation, duplicate checks and proposed regression coverage. No secrets or hostile payloads will be published.
Author
Owner

Started perf-guards on job/perf-guards, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Read CLAUDE.md, CONTEXT.md, DESIGN and #663. Scope: audit and mechanical guard specifications only; no product edits or guard implementation. Will inspect merge-round-7a and relevant round-7b branches. DESIGN numbering differs: Instant interactions is §58 on job/instant-663; merge-round-7a uses §58 for agent discovery. Will cite #663 and section title.

Started perf-guards on `job/perf-guards`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (origin/dev). Read CLAUDE.md, CONTEXT.md, DESIGN and #663. Scope: audit and mechanical guard specifications only; no product edits or guard implementation. Will inspect merge-round-7a and relevant round-7b branches. DESIGN numbering differs: Instant interactions is §58 on job/instant-663; merge-round-7a uses §58 for agent discovery. Will cite #663 and section title.
Author
Owner

Started #663 bundle/loading audit on job/perf-arch-bundle, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). No product edits. I will inspect round 7a and the round 7b queue for changes that affect findings. DESIGN §58 is absent on this base; on round 7a §58 describes agent discovery (#630), not #663. The audit therefore uses #663 rules 1–9 plus DESIGN §§18, 38 and 44. Findings and proposed route budgets will be recorded in audit-findings.md and linked here.

Started #663 bundle/loading audit on `job/perf-arch-bundle`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). No product edits. I will inspect round 7a and the round 7b queue for changes that affect findings. DESIGN §58 is absent on this base; on round 7a §58 describes agent discovery (#630), not #663. The audit therefore uses #663 rules 1–9 plus DESIGN §§18, 38 and 44. Findings and proposed route budgets will be recorded in `audit-findings.md` and linked here.
Author
Owner

Sharing security audit started on job/sec-sharing, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). This is the sec-sharing job supplied by the orchestrator. The prompt points to #663 and DESIGN §58; #663 is a performance issue, §58 is absent on this base, and the staged §58 covers agent discovery. The audit uses the explicit security contract and DESIGN §§22, 26, 48 and 54. No product edits, push, deploy or live offensive probes. Findings will distinguish source evidence from runtime evidence and check #707 and the queued branches before filing duplicates.

Sharing security audit started on `job/sec-sharing`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). This is the sec-sharing job supplied by the orchestrator. The prompt points to #663 and DESIGN §58; #663 is a performance issue, §58 is absent on this base, and the staged §58 covers agent discovery. The audit uses the explicit security contract and DESIGN §§22, 26, 48 and 54. No product edits, push, deploy or live offensive probes. Findings will distinguish source evidence from runtime evidence and check #707 and the queued branches before filing duplicates.
Author
Owner

Source review findings filed after duplicate searches: #734 (BLOCKER: owner re-enrolment boundary), #735 (BLOCKER: OIDC authentication age), #737 (BLOCKER: retained ceremony state), and #738 (recovery timing privacy). All remain in origin/job/merge-round-7a at 2f4482ded0. No product changes or live exploit tests. Audit notes committed in 885ed23a9 and 133142a92. Final minimal verification is in progress.

Source review findings filed after duplicate searches: #734 (BLOCKER: owner re-enrolment boundary), #735 (BLOCKER: OIDC authentication age), #737 (BLOCKER: retained ceremony state), and #738 (recovery timing privacy). All remain in origin/job/merge-round-7a at 2f4482ded066d9c5d9c59130377907f7fd2916c9. No product changes or live exploit tests. Audit notes committed in 885ed23a9 and 133142a92. Final minimal verification is in progress.
Author
Owner

Audit checkpoint fcc975faa: source review found two new defects, filed separately as #755 (public download identity/current-grant checks after the namespace lock wait) and #756 (public password work runs synchronously on async request workers without a shared work budget). Both reports separate source evidence from reasoned impact and specify regression requirements; no runtime exploit or load measurement is claimed.

#707 already owns the Photos library-scope and stale Stack findings. #479/#461 already own the removal of public Edit and the new sharing expiry rules. A candidate narrowed App Password/Photos gap was rejected: the production route guard keeps Home-prefix App Passwords on Files.

Audit checkpoint `fcc975faa`: source review found two new defects, filed separately as #755 (public download identity/current-grant checks after the namespace lock wait) and #756 (public password work runs synchronously on async request workers without a shared work budget). Both reports separate source evidence from reasoned impact and specify regression requirements; no runtime exploit or load measurement is claimed. #707 already owns the Photos library-scope and stale Stack findings. #479/#461 already own the removal of public Edit and the new sharing expiry rules. A candidate narrowed App Password/Photos gap was rejected: the production route guard keeps Home-prefix App Passwords on Files.
Author
Owner

Guard audit findings are committed at 9466be963 in audit-findings.md. Notes OFFSET remains at round-7a notes/src/lib.rs:3722 (product correction already #703). Queued job/maillayouts f9f360e68 bench/blaze.mjs:394–415 computes incomplete/mismatched frames, but its CLI only returns the report, so executing the harness does not itself enforce warm completeness. Guard specifications will reuse #641 and add strict result checks. Duplicate searches covered all states for guard, PERF:, perf-lint, mechanical and bundle; #497 owns bundle reduction, not a deterministic build guard. Eight specifications cover inventory/exceptions, list bounds, read-path IO, browser work, shared contracts, bounded render/blaze, bundle bytes and required open profiles. No product changes or measurements. Decisions: deterministic checks fail required CI; measured wall-clock budgets retain the non-blocking periodic policy in CLAUDE.md and Instant interactions.

Guard audit findings are committed at `9466be963` in audit-findings.md. Notes OFFSET remains at round-7a notes/src/lib.rs:3722 (product correction already #703). Queued job/maillayouts f9f360e68 bench/blaze.mjs:394–415 computes incomplete/mismatched frames, but its CLI only returns the report, so executing the harness does not itself enforce warm completeness. Guard specifications will reuse #641 and add strict result checks. Duplicate searches covered all states for guard, PERF:, perf-lint, mechanical and bundle; #497 owns bundle reduction, not a deterministic build guard. Eight specifications cover inventory/exceptions, list bounds, read-path IO, browser work, shared contracts, bounded render/blaze, bundle bytes and required open profiles. No product changes or measurements. Decisions: deterministic checks fail required CI; measured wall-clock budgets retain the non-blocking periodic policy in CLAUDE.md and Instant interactions.
Author
Owner

Confirmed code-trace findings: #759 (BLOCKER: push delivers Event summaries / Log titles after session end), #765 (BLOCKER: fresh versioned private HTTP thumbnails skip Share revoke checks), #766 (external Note images load directly), #767 (queued Mail reader caches retain bodies and late completions after session-ended, proven with synthetic data). Duplicate review: #555 is the earlier cleanup work; #449 concerns physical server thumbnails; #726 owns the Mail proxy. No product edits. First audit commit: c89c1ba22. Fetched origin and merged origin/dev once before final checks: Already up to date. Web gates cannot run without installed TypeScript/Vitest; exact output will be in final report.

Confirmed code-trace findings: #759 (BLOCKER: push delivers Event summaries / Log titles after session end), #765 (BLOCKER: fresh versioned private HTTP thumbnails skip Share revoke checks), #766 (external Note images load directly), #767 (queued Mail reader caches retain bodies and late completions after session-ended, proven with synthetic data). Duplicate review: #555 is the earlier cleanup work; #449 concerns physical server thumbnails; #726 owns the Mail proxy. No product edits. First audit commit: c89c1ba22. Fetched origin and merged origin/dev once before final checks: Already up to date. Web gates cannot run without installed TypeScript/Vitest; exact output will be in final report.
Author
Owner

Completed the read-only sec-auth audit requested under #663.

Built: source-evidence audit report, control trace, pending-branch checks,
duplicate searches and four self-contained corrective issues. No product edits.

Files: audit-findings.md only. Local build and issue-search logs remain in
gitignored artifacts/. No review artifacts were committed.

Branch: job/sec-auth. Head: d04ee94b84.
Atomic commits: 885ed23a9, 133142a92, d04ee94b8.
Final origin/dev: c4a61e8cf0.
Round 7a reviewed: 2f4482ded0.

Findings:

  • #734 BLOCKER: re-enrolment must protect the owner authority boundary.
  • #735 BLOCKER: OIDC elevation must validate recent provider authentication.
  • #737 BLOCKER: pending passkey and OIDC ceremony state needs resource bounds.
  • #738 recovery verification skips dummy work for unknown Users.

Final merge output, verbatim:

Already up to date.

Verification output, verbatim:

cargo fmt --check exit: 0
git diff --check exit: 0
     Removed 218 files, 93.3MiB total
cargo clean exit: 0

The formatting command produced no output; the exit line comes from its shell
wrapper. cargo test -p calternal-auth was cancelled during its cold dependency
build, exit 143, with no tests run. Host load averages were 69.79, 62.41, 55.61.
The log is artifacts/auth-test.log. No passing test claim is made. No crate
source changed, so clippy, server and web gates were not run. No web build
output was present. The job target was cleaned.

Known gaps: no live-server adversarial round, provider integration test,
browser checks or performance measurements. Findings are reasoned from source.
Product fixes and the specified regression tests remain with corrective jobs.
No push, deployment, issue closure or product-branch merge occurred.

Decisions: keep the explicitly requested auth scope although #663 is the
performance issue and §58 differs between branches. Use DESIGN §§7 and 21
for auth policy and the owner's security/resource-exhaustion merge bar.
Keep product code unchanged. Cancel the optional cold test build to keep host
use low; do not treat compilation as a test pass.

UX gaps closed: none; no UI changed.
UX gaps left: no UI runtime audit; the four security defects remain open.

Completed the read-only sec-auth audit requested under #663. Built: source-evidence audit report, control trace, pending-branch checks, duplicate searches and four self-contained corrective issues. No product edits. Files: audit-findings.md only. Local build and issue-search logs remain in gitignored artifacts/. No review artifacts were committed. Branch: job/sec-auth. Head: d04ee94b84612b68043ac6edad185bd37eba2ba6. Atomic commits: 885ed23a9, 133142a92, d04ee94b8. Final origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Round 7a reviewed: 2f4482ded066d9c5d9c59130377907f7fd2916c9. Findings: - #734 BLOCKER: re-enrolment must protect the owner authority boundary. - #735 BLOCKER: OIDC elevation must validate recent provider authentication. - #737 BLOCKER: pending passkey and OIDC ceremony state needs resource bounds. - #738 recovery verification skips dummy work for unknown Users. Final merge output, verbatim: ```text Already up to date. ``` Verification output, verbatim: ```text cargo fmt --check exit: 0 git diff --check exit: 0 Removed 218 files, 93.3MiB total cargo clean exit: 0 ``` The formatting command produced no output; the exit line comes from its shell wrapper. cargo test -p calternal-auth was cancelled during its cold dependency build, exit 143, with no tests run. Host load averages were 69.79, 62.41, 55.61. The log is artifacts/auth-test.log. No passing test claim is made. No crate source changed, so clippy, server and web gates were not run. No web build output was present. The job target was cleaned. Known gaps: no live-server adversarial round, provider integration test, browser checks or performance measurements. Findings are reasoned from source. Product fixes and the specified regression tests remain with corrective jobs. No push, deployment, issue closure or product-branch merge occurred. Decisions: keep the explicitly requested auth scope although #663 is the performance issue and §58 differs between branches. Use DESIGN §§7 and 21 for auth policy and the owner's security/resource-exhaustion merge bar. Keep product code unchanged. Cancel the optional cold test build to keep host use low; do not treat compilation as a test pass. UX gaps closed: none; no UI changed. UX gaps left: no UI runtime audit; the four security defects remain open.
Author
Owner

Findings filed: #728 (BLOCKER, private Index/snapshot filesystem boundary), #733 (Mail provider error text in logs), #732 (deploy rollback, raw log copying and image retention). Reused #242 for container privileges and #601 for process TLS checks. Lightweight admin-classification check: 13 passed, 1 failed for missing Notes read/write/full App Password fixtures; existing #716 owns that failure. Product files and test expectations are unchanged. Full coverage and verbatim check output are in audit-findings.md.

Findings filed: #728 (BLOCKER, private Index/snapshot filesystem boundary), #733 (Mail provider error text in logs), #732 (deploy rollback, raw log copying and image retention). Reused #242 for container privileges and #601 for process TLS checks. Lightweight admin-classification check: 13 passed, 1 failed for missing Notes read/write/full App Password fixtures; existing #716 owns that failure. Product files and test expectations are unchanged. Full coverage and verbatim check output are in audit-findings.md.
Author
Owner

Server request audit findings committed at 60baf4bde; base c4a61e8cf. Existing HTTP gaps remain assigned to #677–#704 and shared primitives #665–#668. New duplicate-checked issues: #780 Notes IMAP all-Note scans under the writer lock; #782 Analytics GET awaits optional cache writes; #783 warm HLS reads hold a global mutex over rendition bookkeeping writes; #784 Tag suggestions build whole-Home sets and pages use OFFSET. All remain in origin/job/merge-round-7a at 2f4482ded. These are source-based impact findings, not perf-VM measurements or confirmed security blockers. Scratch EXPLAIN QUERY PLAN also shows the Notes title order creates a temporary sort tree; evidence will be added to #703. No product files changed.

Server request audit findings committed at 60baf4bde; base c4a61e8cf. Existing HTTP gaps remain assigned to #677–#704 and shared primitives #665–#668. New duplicate-checked issues: #780 Notes IMAP all-Note scans under the writer lock; #782 Analytics GET awaits optional cache writes; #783 warm HLS reads hold a global mutex over rendition bookkeeping writes; #784 Tag suggestions build whole-Home sets and pages use OFFSET. All remain in origin/job/merge-round-7a at 2f4482ded. These are source-based impact findings, not perf-VM measurements or confirmed security blockers. Scratch EXPLAIN QUERY PLAN also shows the Notes title order creates a temporary sort tree; evidence will be added to #703. No product files changed.
Author
Owner

Protocol source review found five merge-blocking gaps. Audit commit: d5b1f5af4. Repair issues: #785 DAV XML depth (recursive tree without a depth guard); #786 missing Notes IMAP IP/User connection permits; #787 active Notes sessions do not enforce App Password expiry; #788 Files/Notes SSE emits paths after authorization ends; #789 legacy MCP session-control/stream access lacks an owner binding (conditional on obtaining the session ID). Each issue gives the audited revision, source evidence, reasoned impact, repair and regression coverage. No hostile payloads, crash threshold or live exploitation claim. The SMTP stub still has no mail delivery path. Committed #486 is not yet wired to a Mail provider; re-audit its final server paths. Duplicate searches found adjacent #457/#500/#587/#668, not these repairs. The findings persist in the relevant 7a source.

Protocol source review found five merge-blocking gaps. Audit commit: d5b1f5af4. Repair issues: #785 DAV XML depth (recursive tree without a depth guard); #786 missing Notes IMAP IP/User connection permits; #787 active Notes sessions do not enforce App Password expiry; #788 Files/Notes SSE emits paths after authorization ends; #789 legacy MCP session-control/stream access lacks an owner binding (conditional on obtaining the session ID). Each issue gives the audited revision, source evidence, reasoned impact, repair and regression coverage. No hostile payloads, crash threshold or live exploitation claim. The SMTP stub still has no mail delivery path. Committed #486 is not yet wired to a Mail provider; re-audit its final server paths. Duplicate searches found adjacent #457/#500/#587/#668, not these repairs. The findings persist in the relevant 7a source.
Author
Owner

Defensive audit complete; product fixes remain open.

Branch: job/sec-admin-deploy. Head: 0fe916da7dbb730eb33226b66093252b05261cf1. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Built: one committed audit report with source evidence, coverage, fix requests and verification limits. Files: audit-findings.md only. Commits: ee38fe5cb (initial findings), 0fe916da7 (coverage and validation). No push or deploy; the required merge of origin/dev was already up to date.

Cleanup output, verbatim:

     Removed 1 file, 356B total

No web build output exists. No Rust/web implementation or test expectation changed.

Admin, configuration and deployment audit

This report records the defensive sec-admin-deploy job. It changes no product
code. The source base is c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev).
The audit date is 2026-10-02. Evidence is from source review unless stated.

Scope and source mismatch

The assigned issue #663 concerns performance, not this security audit. The
base DESIGN ends at §57. The queued job/instant-663 adds §58, but that section
also concerns performance. Use DESIGN §§2, 7, 15, 16, 20, 21 and 50 as the
security bar. No new product decision is made here.

Review the changed security paths in origin/job/merge-round-7a and the
round-7b entries in the external paused-queue.txt. Do not merge those job
branches into this audit branch.

Findings

  1. BLOCKER: private Index files do not have a private filesystem boundary.
    Filed as #728.
    crates/calternal-fs/src/root.rs:1029 creates directories with mode 0755.
    mkdir_internal uses that same function, including .system/backups.
    crates/calternal-db/src/db.rs:46 lets SQLite create the Index without an
    explicit private mode. crates/calternal-db/src/snapshot.rs:57 lets SQLite
    create snapshots without an explicit private mode. The deploy script
    creates the host data directories without a private mode (line 38).
    The resolved libsqlite3-sys 0.37.0 source sets
    SQLITE_DEFAULT_FILE_PERMISSIONS to 0644; its WAL/journal creation copies
    the database mode. This confirms the default by source, without reading
    any real Index.
    With a normal 0022 umask and traversable host parents, another local OS
    user can read the Index and snapshots. These contain Security state,
    personal data and Instance secrets. This is a local filesystem issue;
    no remote API bypass is claimed. Protect .system with mode 0700 through
    calternal-fs, including existing directories. Protect the Index, WAL,
    journal and snapshot files with mode 0600. Add mode tests under a 0022
    umask and an upgrade test with existing permissive directories.

  2. Mail command failures write provider text to logs.
    Filed as #733.
    crates/plugins/mail/src/sync.rs:214 and :1222 log the first 300
    characters of a dependency error. A length limit does not remove private
    values. Mail uses the vendored async-imap 0.11.3. Its
    src/client.rs:1513-1514 puts provider code and information into
    No(String) and Bad(String). src/parse.rs:141-144 does the same.
    These paths survive the wire-log privacy patch. The comment that error
    strings contain no private data does not hold for command. The generic
    logged_step also has no type or value restriction. Log a static
    error class and the static step instead. Test log capture with synthetic
    provider errors; verify no response text or bytes reach the log.

  3. A failed deploy has no rollback and copies raw service logs.
    Filed as #732.
    deploy/deploy-cloud.sh:36-49 replaces the mutable image tag, installs the
    Quadlet, restarts, then only exits on health failure. It does not retain
    and restore the previous image ID and Quadlet. It prints 60 raw journal
    lines. crates/calternal-auth/src/api.rs:202 intentionally logs the
    first-owner setup URL (DESIGN §7). Thus the diagnostic copy can carry a
    setup credential to the invoking job's output. Do not copy raw service
    logs. Print a fixed failure and a local diagnostic instruction. Keep
    immutable release identities and rollback state. Check migration
    compatibility before any binary rollback. Test failure paths with fake
    service and image commands; do not deploy to test this change.
    There is also no image retention step. The script exports only the mutable
    main tag to the VM. Old image layers can remain after each replacement.
    Keep a small, explicit set of release IDs and remove only obsolete release
    images after success. Do not use a broad prune that removes rollback or
    Agent images. Disk growth is reasoned; no disk-exhaustion event was measured.

Existing issues

The runtime adds SYS_ADMIN, FUSE, TUN and an unmasked proc filesystem. It has
no read-only root filesystem, tmpfs declaration, capability-drop-all or
no-new-privileges setting. Issue #242 already owns this group and the required
architecture decision. Do not file a duplicate or change that design here.
The unchanged admin-classification fixture failure is already tracked in
#716. No duplicate was filed.

Coverage

Area Evidence and result
Admin authority authz.rs:35-73 requires a User, admin scope and owner/admin Role before extraction. wire.rs:2992-3064 attaches guards to admin routes. Authority and config mutations require a recent assertion. Backup trigger requires the ordinary admin guard. No new bypass found by source review.
Auth administration calternal-auth/src/api.rs:275-322 checks scope, Role and freshness. Cookie mutations also require the exact Instance Origin (:360-372). Sessions use Secure, HttpOnly, SameSite cookies. No new bypass found by source review.
Configuration wire.rs:1944-1951 validates provider discovery before disk installation. root.rs:553-570 writes mode 0600, syncs and renames relative to a held directory. Secrets remain outside Homes. Config GET and TOML export return OIDC client secrets to authorized admins; no access for standard Users is intended.
Secret files root.rs:507-548 uses confined, no-symlink opens and bounded regular files. Profile signing and Notes TLS reject key files readable by group or others. Config parse errors have value-redaction tests (wire.rs:7259-7306). Index files need #728.
Container The main runtime uses UID/GID 65536 and a digest-pinned Debian 13 base. Nested Podman needs the privileges tracked in #242. The root Containerfile is a separate build path, with UID 10001 and a mutable Debian tag. Neither build file proves the running host state.
Image freshness Runtime libheif is 1.23.5, libde265 is 1.1.3 and libvips is 8.16.1. libheif upstream identifies 1.23.5 as a security/bugfix release. Build-time apt installs update distribution packages; there is no runtime-image SBOM or installed-package scan in this audit. A pinned digest alone does not prove freshness.
Deploy and pruning #732 covers failure recovery, credential-bearing diagnostic output and bounded release retention. No deploy command was run.
Backups snapshot.rs:55-80 uses VACUUM INTO, file sync, rename, directory sync and retention. tests/queue.rs:634-675 opens the snapshot and checks one durable queue row. This is a database restore test, not a full Instance restore drill.
Backup encryption The in-app snapshot is a plain SQLite file. DESIGN §16 deliberately leaves an external backup plugin for later. The cloud README says PBS backs up both disks; it does not establish encryption, key custody or successful recovery. Those external controls are unverified, not claimed absent.
Restore completeness A full drill must restore both /data and /userdata, the matching Index snapshot, config and external key files. Verify Security state, shares and encrypted Connected Account credentials before opening network access. The repository has no full procedure or evidence of that drill. No production restore was attempted.
TLS Notes IMAP uses safe rustls protocol defaults, bounded sockets and key-file checks (notes_imap.rs:108-159). Cargo.lock pins rustls 0.23.45, as required by DESIGN §3. HTTP TLS, redirects, HSTS and certificates are in the private Traefik configuration, outside this checkout. #601 owns real process TLS checks.
Headers and CSP wire.rs:1497 puts security::baseline outside the combined main router. security.rs:61-99 sets nosniff, referrer, frame and permissions headers. API/DAV default to a no-script CSP and private/no-store. Built SPA scripts get hashes; user-byte routes keep their stricter policy. Source review does not replace a production header capture.
Logging and crash reports Write timing uses static operation classes (main.rs:489-546). No crash upload client or custom panic reporter was found in the reviewed server. Default panic output and OS core-dump policy are not certified. #733 covers the specific Mail leak; #732 covers raw diagnostic copying. The bootstrap setup URL is an explicit DESIGN §7 exception in local service logs.

Upstream checks: SQLite VACUUM documentation,
libheif releases, and
rustls releases. No dependency
version was changed. No vulnerability-free image claim is made.

Queued branch review

origin/job/merge-round-7a changes the auth cache, Connected Account migration,
MCP Events and server assembly. Review of the changed assembly shows the
same outer header/session layers. The config, snapshot permission and
deploy findings remain. Mail's provider-text constructors also remain.
The Connected Account routes use bounded bodies and structured migration
error codes. This audit does not certify all new plugin authorization paths.

The round-7b list contains docs, retained-data libraries, Notes projections,
Mail Undo, UI changes and test harnesses. The directly relevant
job/instant-663 confirms that §58 is performance policy, not an admin
security standard. job/imaptest-625 keeps the command-error constructors
reported in #733. The other listed branches do not add an admin/config,
snapshot or deploy fix for these findings. They need their own cross-User
and API checks at merge. No queued branch was merged here.

Reviewed ref identities:

  • origin/job/merge-round-7a: 2f4482ded066d9c5d9c59130377907f7fd2916c9.
  • origin/job/instant-663: e62249dedcc2c7d108e4432596d40aee6f5a4bc8.
  • origin/job/imaptest-625: f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3.

Decisions

  • Follow the explicit defensive job brief despite the issue/section mismatch.
  • Leave product fixes to the filed issues, as required by this read-mostly job.
  • Treat #728 as merge-blocking under the authorization-hole bar. Its host
    exposure depends on parent permissions; actual host access was not measured.
  • Reuse #242 and #601. Report external encryption and TLS as unverified.
  • No new route, feature or background job was added. No performance profile
    or VM measurement is required for this documentation change.

UX gaps

No UI was changed. No UX gap was closed or introduced. Screenshot checks do
not apply to this report.

Limits

No live deployment, production files, credentials or personal records were
read. No vulnerability payload, request storm or remote probe was run.
This report does not certify the private Traefik, host firewall, disk
encryption or Proxmox Backup Server configuration.

Validation

git fetch origin and git merge origin/dev were run once. The merge said
Already up to date. The final upstream base remains
c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

No Rust crate or web code changed. Per-crate clippy/test and web check/test
were not run. No image build was needed. The source-only admin classification
check runs without credentials or a server. Its fixture failure is on the
unchanged base: Notes App Password read, write and full classes are missing
(existing issue #716).
Do not change its expectations to pass this audit.

Gate output, verbatim (the runner prints each command and its exit status):

$ git diff --check
exit: 0
$ bash -n deploy/deploy-cloud.sh
exit: 0
$ cargo fmt --check
exit: 0
$ python3 -m unittest discover -s tests/adversarial -p test_admin_classification.py
.F............
======================================================================
FAIL: test_app_password_fixture_covers_each_valid_scope_class (test_admin_classification.AdminClassificationGuard.test_app_password_fixture_covers_each_valid_scope_class)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/kayg/Developer/calternal-wt/sec-admin-deploy/tests/adversarial/test_admin_classification.py", line 157, in test_app_password_fixture_covers_each_valid_scope_class
    self.assertEqual(actual, expected)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
AssertionError: Items in the second set but not the first:
('notes', 'full')
('notes', 'write')
('notes', 'read')

----------------------------------------------------------------------
Ran 14 tests in 1.448s

FAILED (failures=1)
exit: 1

This audit does not claim that all gates pass or that the filed security
findings are fixed. A live adversarial round is not required by this report:
the merge changed no API and the job prohibits product edits and deployment.

Defensive audit complete; product fixes remain open. Branch: `job/sec-admin-deploy`. Head: `0fe916da7dbb730eb33226b66093252b05261cf1`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Built: one committed audit report with source evidence, coverage, fix requests and verification limits. Files: `audit-findings.md` only. Commits: `ee38fe5cb` (initial findings), `0fe916da7` (coverage and validation). No push or deploy; the required merge of origin/dev was already up to date. Cleanup output, verbatim: ```text Removed 1 file, 356B total ``` No web build output exists. No Rust/web implementation or test expectation changed. # Admin, configuration and deployment audit This report records the defensive `sec-admin-deploy` job. It changes no product code. The source base is `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). The audit date is 2026-10-02. Evidence is from source review unless stated. ## Scope and source mismatch The assigned issue #663 concerns performance, not this security audit. The base DESIGN ends at §57. The queued `job/instant-663` adds §58, but that section also concerns performance. Use DESIGN §§2, 7, 15, 16, 20, 21 and 50 as the security bar. No new product decision is made here. Review the changed security paths in `origin/job/merge-round-7a` and the round-7b entries in the external `paused-queue.txt`. Do not merge those job branches into this audit branch. ## Findings 1. **BLOCKER: private Index files do not have a private filesystem boundary.** Filed as [#728](https://git.kayg.org/kayg/calternal/issues/728). `crates/calternal-fs/src/root.rs:1029` creates directories with mode 0755. `mkdir_internal` uses that same function, including `.system/backups`. `crates/calternal-db/src/db.rs:46` lets SQLite create the Index without an explicit private mode. `crates/calternal-db/src/snapshot.rs:57` lets SQLite create snapshots without an explicit private mode. The deploy script creates the host data directories without a private mode (line 38). The resolved libsqlite3-sys 0.37.0 source sets `SQLITE_DEFAULT_FILE_PERMISSIONS` to 0644; its WAL/journal creation copies the database mode. This confirms the default by source, without reading any real Index. With a normal 0022 umask and traversable host parents, another local OS user can read the Index and snapshots. These contain Security state, personal data and Instance secrets. This is a local filesystem issue; no remote API bypass is claimed. Protect `.system` with mode 0700 through `calternal-fs`, including existing directories. Protect the Index, WAL, journal and snapshot files with mode 0600. Add mode tests under a 0022 umask and an upgrade test with existing permissive directories. 2. **Mail command failures write provider text to logs.** Filed as [#733](https://git.kayg.org/kayg/calternal/issues/733). `crates/plugins/mail/src/sync.rs:214` and `:1222` log the first 300 characters of a dependency error. A length limit does not remove private values. Mail uses the vendored async-imap 0.11.3. Its `src/client.rs:1513-1514` puts provider `code` and `information` into `No(String)` and `Bad(String)`. `src/parse.rs:141-144` does the same. These paths survive the wire-log privacy patch. The comment that error strings contain no private data does not hold for `command`. The generic `logged_step` also has no type or value restriction. Log a static error class and the static step instead. Test log capture with synthetic provider errors; verify no response text or bytes reach the log. 3. **A failed deploy has no rollback and copies raw service logs.** Filed as [#732](https://git.kayg.org/kayg/calternal/issues/732). `deploy/deploy-cloud.sh:36-49` replaces the mutable image tag, installs the Quadlet, restarts, then only exits on health failure. It does not retain and restore the previous image ID and Quadlet. It prints 60 raw journal lines. `crates/calternal-auth/src/api.rs:202` intentionally logs the first-owner setup URL (DESIGN §7). Thus the diagnostic copy can carry a setup credential to the invoking job's output. Do not copy raw service logs. Print a fixed failure and a local diagnostic instruction. Keep immutable release identities and rollback state. Check migration compatibility before any binary rollback. Test failure paths with fake service and image commands; do not deploy to test this change. There is also no image retention step. The script exports only the mutable `main` tag to the VM. Old image layers can remain after each replacement. Keep a small, explicit set of release IDs and remove only obsolete release images after success. Do not use a broad prune that removes rollback or Agent images. Disk growth is reasoned; no disk-exhaustion event was measured. ## Existing issues The runtime adds SYS_ADMIN, FUSE, TUN and an unmasked proc filesystem. It has no read-only root filesystem, tmpfs declaration, capability-drop-all or no-new-privileges setting. Issue #242 already owns this group and the required architecture decision. Do not file a duplicate or change that design here. The unchanged admin-classification fixture failure is already tracked in [#716](https://git.kayg.org/kayg/calternal/issues/716). No duplicate was filed. ## Coverage | Area | Evidence and result | | --- | --- | | Admin authority | `authz.rs:35-73` requires a User, admin scope and owner/admin Role before extraction. `wire.rs:2992-3064` attaches guards to admin routes. Authority and config mutations require a recent assertion. Backup trigger requires the ordinary admin guard. No new bypass found by source review. | | Auth administration | `calternal-auth/src/api.rs:275-322` checks scope, Role and freshness. Cookie mutations also require the exact Instance Origin (`:360-372`). Sessions use Secure, HttpOnly, SameSite cookies. No new bypass found by source review. | | Configuration | `wire.rs:1944-1951` validates provider discovery before disk installation. `root.rs:553-570` writes mode 0600, syncs and renames relative to a held directory. Secrets remain outside Homes. Config GET and TOML export return OIDC client secrets to authorized admins; no access for standard Users is intended. | | Secret files | `root.rs:507-548` uses confined, no-symlink opens and bounded regular files. Profile signing and Notes TLS reject key files readable by group or others. Config parse errors have value-redaction tests (`wire.rs:7259-7306`). Index files need #728. | | Container | The main runtime uses UID/GID 65536 and a digest-pinned Debian 13 base. Nested Podman needs the privileges tracked in #242. The root Containerfile is a separate build path, with UID 10001 and a mutable Debian tag. Neither build file proves the running host state. | | Image freshness | Runtime libheif is 1.23.5, libde265 is 1.1.3 and libvips is 8.16.1. libheif upstream identifies 1.23.5 as a security/bugfix release. Build-time apt installs update distribution packages; there is no runtime-image SBOM or installed-package scan in this audit. A pinned digest alone does not prove freshness. | | Deploy and pruning | #732 covers failure recovery, credential-bearing diagnostic output and bounded release retention. No deploy command was run. | | Backups | `snapshot.rs:55-80` uses VACUUM INTO, file sync, rename, directory sync and retention. `tests/queue.rs:634-675` opens the snapshot and checks one durable queue row. This is a database restore test, not a full Instance restore drill. | | Backup encryption | The in-app snapshot is a plain SQLite file. DESIGN §16 deliberately leaves an external backup plugin for later. The cloud README says PBS backs up both disks; it does not establish encryption, key custody or successful recovery. Those external controls are unverified, not claimed absent. | | Restore completeness | A full drill must restore both `/data` and `/userdata`, the matching Index snapshot, config and external key files. Verify Security state, shares and encrypted Connected Account credentials before opening network access. The repository has no full procedure or evidence of that drill. No production restore was attempted. | | TLS | Notes IMAP uses safe rustls protocol defaults, bounded sockets and key-file checks (`notes_imap.rs:108-159`). Cargo.lock pins rustls 0.23.45, as required by DESIGN §3. HTTP TLS, redirects, HSTS and certificates are in the private Traefik configuration, outside this checkout. #601 owns real process TLS checks. | | Headers and CSP | `wire.rs:1497` puts `security::baseline` outside the combined main router. `security.rs:61-99` sets nosniff, referrer, frame and permissions headers. API/DAV default to a no-script CSP and private/no-store. Built SPA scripts get hashes; user-byte routes keep their stricter policy. Source review does not replace a production header capture. | | Logging and crash reports | Write timing uses static operation classes (`main.rs:489-546`). No crash upload client or custom panic reporter was found in the reviewed server. Default panic output and OS core-dump policy are not certified. #733 covers the specific Mail leak; #732 covers raw diagnostic copying. The bootstrap setup URL is an explicit DESIGN §7 exception in local service logs. | Upstream checks: [SQLite VACUUM documentation](https://www.sqlite.org/lang_vacuum.html), [libheif releases](https://github.com/strukturag/libheif/releases), and [rustls releases](https://github.com/rustls/rustls/releases). No dependency version was changed. No vulnerability-free image claim is made. ## Queued branch review `origin/job/merge-round-7a` changes the auth cache, Connected Account migration, MCP Events and server assembly. Review of the changed assembly shows the same outer header/session layers. The config, snapshot permission and deploy findings remain. Mail's provider-text constructors also remain. The Connected Account routes use bounded bodies and structured migration error codes. This audit does not certify all new plugin authorization paths. The round-7b list contains docs, retained-data libraries, Notes projections, Mail Undo, UI changes and test harnesses. The directly relevant `job/instant-663` confirms that §58 is performance policy, not an admin security standard. `job/imaptest-625` keeps the command-error constructors reported in #733. The other listed branches do not add an admin/config, snapshot or deploy fix for these findings. They need their own cross-User and API checks at merge. No queued branch was merged here. Reviewed ref identities: - `origin/job/merge-round-7a`: `2f4482ded066d9c5d9c59130377907f7fd2916c9`. - `origin/job/instant-663`: `e62249dedcc2c7d108e4432596d40aee6f5a4bc8`. - `origin/job/imaptest-625`: `f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3`. ## Decisions - Follow the explicit defensive job brief despite the issue/section mismatch. - Leave product fixes to the filed issues, as required by this read-mostly job. - Treat #728 as merge-blocking under the authorization-hole bar. Its host exposure depends on parent permissions; actual host access was not measured. - Reuse #242 and #601. Report external encryption and TLS as unverified. - No new route, feature or background job was added. No performance profile or VM measurement is required for this documentation change. ## UX gaps No UI was changed. No UX gap was closed or introduced. Screenshot checks do not apply to this report. ## Limits No live deployment, production files, credentials or personal records were read. No vulnerability payload, request storm or remote probe was run. This report does not certify the private Traefik, host firewall, disk encryption or Proxmox Backup Server configuration. ## Validation `git fetch origin` and `git merge origin/dev` were run once. The merge said `Already up to date.` The final upstream base remains `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. No Rust crate or web code changed. Per-crate clippy/test and web check/test were not run. No image build was needed. The source-only admin classification check runs without credentials or a server. Its fixture failure is on the unchanged base: Notes App Password read, write and full classes are missing (existing issue #716). Do not change its expectations to pass this audit. Gate output, verbatim (the runner prints each command and its exit status): ```text $ git diff --check exit: 0 $ bash -n deploy/deploy-cloud.sh exit: 0 $ cargo fmt --check exit: 0 $ python3 -m unittest discover -s tests/adversarial -p test_admin_classification.py .F............ ====================================================================== FAIL: test_app_password_fixture_covers_each_valid_scope_class (test_admin_classification.AdminClassificationGuard.test_app_password_fixture_covers_each_valid_scope_class) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/kayg/Developer/calternal-wt/sec-admin-deploy/tests/adversarial/test_admin_classification.py", line 157, in test_app_password_fixture_covers_each_valid_scope_class self.assertEqual(actual, expected) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ AssertionError: Items in the second set but not the first: ('notes', 'full') ('notes', 'write') ('notes', 'read') ---------------------------------------------------------------------- Ran 14 tests in 1.448s FAILED (failures=1) exit: 1 ``` This audit does not claim that all gates pass or that the filed security findings are fixed. A live adversarial round is not required by this report: the merge changed no API and the job prohibits product edits and deployment.
Author
Owner

Audit checkpoint: a42ad505d plus the retained-document slice committed on job/perf-arch-memory. Filed #761 collaboration queue, #762 Unsplash bytes/expiry, #763 Mail IDLE occupying sync capacity, #764 empty-worker polling, #807 Money parse-cache bytes, #808 Notes IMAP SELECT/refresh residency, #809 historical User lock registries. All are source-confirmed; numeric effects are reasoned allocation/work estimates, not new VM RSS measurements. Search full-manifest costs are already owned by #496/#503 and fixed in round 7a; the old MCP Events 10 Hz loop is also removed there. No product changes.

Audit checkpoint: a42ad505d plus the retained-document slice committed on job/perf-arch-memory. Filed #761 collaboration queue, #762 Unsplash bytes/expiry, #763 Mail IDLE occupying sync capacity, #764 empty-worker polling, #807 Money parse-cache bytes, #808 Notes IMAP SELECT/refresh residency, #809 historical User lock registries. All are source-confirmed; numeric effects are reasoned allocation/work estimates, not new VM RSS measurements. Search full-manifest costs are already owned by #496/#503 and fixed in round 7a; the old MCP Events 10 Hz loop is also removed there. No product changes.
Author
Owner

sec-fs source findings recorded in audit-findings.md (commit 58a3b19f5):

  • #779 BLOCKER: HLS measures and evicts output only after transcode completion; active output and round-7a disk snapshots have no free-space reservation.
  • #781 BLOCKER: the PDF search child has a two-second deadline but no hard memory budget; parser/page/decompression allocation precedes the output cap. Exact lopdf 0.42.0 source was checked on the web.
  • #801 BLOCKER: public XMP Sidecar checks run only at upload creation, not against the final destination under the install lock.
  • #802 reliability follow-up: named pre-journal temporaries have no cancellation-safe guard; Home leftovers remain hidden and count toward quota.
  • #621 existing issue extended: restore does not use the shared NFC/full-case-fold sibling conflict check.

Each new issue records revisions, file:line evidence, reasoned impact, a concrete fix and small defensive test requirements. These are source-confirmed boundary gaps, not claims of a reproduced crash, disk-full event or unauthorized metadata change. No product edits or live-server attack workflow were used. #501 already has its immutable-input fix in round 7a and was not duplicated.

sec-fs source findings recorded in audit-findings.md (commit 58a3b19f5): - #779 BLOCKER: HLS measures and evicts output only after transcode completion; active output and round-7a disk snapshots have no free-space reservation. - #781 BLOCKER: the PDF search child has a two-second deadline but no hard memory budget; parser/page/decompression allocation precedes the output cap. Exact lopdf 0.42.0 source was checked on the web. - #801 BLOCKER: public XMP Sidecar checks run only at upload creation, not against the final destination under the install lock. - #802 reliability follow-up: named pre-journal temporaries have no cancellation-safe guard; Home leftovers remain hidden and count toward quota. - #621 existing issue extended: restore does not use the shared NFC/full-case-fold sibling conflict check. Each new issue records revisions, file:line evidence, reasoned impact, a concrete fix and small defensive test requirements. These are source-confirmed boundary gaps, not claims of a reproduced crash, disk-full event or unauthorized metadata change. No product edits or live-server attack workflow were used. #501 already has its immutable-input fix in round 7a and was not duplicated.
Author
Owner

sec-browser final report

Built: documentation-only browser security audit; no product edits.
Files: audit-findings.md.
Branch: job/sec-browser.
Head SHA: 53b430da8c
Commits: c89c1ba22 (initial findings), 53b430da8 (coverage, issue links, verification).

Findings: #759 and #765 marked BLOCKER; #766 and #767 are privacy/cleanup follow-ups. No push or deploy. Required origin/dev merge returned Already up to date.

Browser security audit

This report records the sec-browser job for #663. It is a read-only review.
It changes no product code. Dates use the environment date, 2026-10-02.

Source and rules

  • Base and audited origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
  • Rules read: CLAUDE.md, CONTEXT.md, and docs/DESIGN.md.
  • #663 is the performance umbrella. The direct job brief defines this audit.
  • DESIGN on dev ends at §57. On round-7a, §58 describes agent discovery.
    job/instant-663 adds the intended performance and access rules in §58.
    This review uses §§21, 45, 54, 57 and that queued §58.
  • Queue source: ~/.local/state/codex-jobs/calternal/paused-queue.txt.
  • Evidence below is a code trace. No hostile payloads, external attacks,
    production access, push, deploy or product edits are part of this job.

Filed findings

B1 — Push survives session end

Status: confirmed by code trace; filed as #759.
Merge class: BLOCKER, private data can reach a signed-out Installation.

apps/web/src/routes/+layout.svelte:670 deletes the browser session, calls
endUserSession, and goes to sign-in. It does not call disablePush.
apps/web/src/lib/userStorage.ts:205 removes the hint and User stores, but
does not remove the browser push subscription. The only unsubscribe path is
apps/web/src/lib/notifications/push.ts:196, for explicit disable.

crates/plugins/notifications/src/store.rs:586 binds a subscription to User
and Installation, without a session. pending_pushes at line 665 joins that
row without checking a live session. push.rs:212 sends the stored title,
body and link. reminders.rs:61 puts an Event summary in the body; line 162
puts a Log title there. apps/web/src/service-worker.ts:55 shows it without
checking the current User. A later User need not enable push to receive it.

Fix: revoke this Installation's delivery authority at session end. Bind push
authority to a revocable session or Installation generation on the server.
Unsubscribe locally even when the session has already expired. Clear shown
notifications and reject delayed payloads for an ended User. Keep all other
Installations working.

Test: use two synthetic Users and a fake push transport. End A's session,
then enqueue an A reminder. Assert no delivery to this Installation. Sign in
B with push off and assert no A system notification. Check session expiry,
remote revoke, pending delivery and a second live A Installation.

B2 — Private thumbnails enter unmanaged HTTP cache

Status: confirmed policy gap by code trace; duplicate search complete;
filed as #765. Merge class: BLOCKER for Share revocation. No claim of
cross-User cookie-cache reuse is made.

crates/plugins/files/src/thumbnails.rs:736 permits versioned thumbnails
with private, max-age=31536000, immutable on both 200 and 304 responses.
It varies on Cookie, which separates changed cookies. That does not cause
revalidation when a Share is revoked with the same cookie. The route checks
Share access before serving, but a fresh browser cache can skip the route.
apps/web/src/lib/appearance/background.svelte.ts:432 uses this variant.
userStorage.clearUser deletes Web Storage, IndexedDB and Cache Storage,
not the browser HTTP cache. Private photo bytes can remain after sign-out.
Round-7a retains the same policy. #449 covers physical server thumbnail
separation, not this browser response policy.

Fix: prevent private bytes from entering unmanaged HTTP cache. Use no-store
and a bounded User cache that cleanup can delete. If HTTP revalidation is
kept for another use, require it on every use and check current access before
304. Cookie variance alone is not a revocation mechanism.

Test: assert the versioned response cannot be reused without an access check.
Use benign synthetic image fixtures for session cleanup and Share revoke.

B3 — Notes load external images in the browser

Status: confirmed by code trace; filed as #766.
Merge class: privacy follow-up; no script execution claim.

apps/web/src/lib/notes/editorHost.ts:71 passes external HTTP(S) images to
the browser. NoteImageView.svelte:39 uses the result as an image source.
crates/calternal-server/src/security.rs:121 permits all HTTPS image
origins. Opening an imported Note can disclose IP, request time and browser
metadata to an image origin. The User does not need to click a link.
Mail's consent-only direct image loads are already owned by #726; do not
create a second Mail image-proxy issue.

Fix: reuse the server image-proxy policy from #726 for Notes. Bound type,
size and time; check public destinations and redirect hops; strip cookies
and Referer. Make protection silent, as the owner requires.

Test: with a benign synthetic remote image, assert no external browser
request and no outbound cookies or Referer. Keep local attachments working.

B4 — Queued Mail caches do not clear at session end

Status: confirmed with a benign lifecycle check; filed as #767.
Merge class: cleanup regression. User-keyed cache identity and the shell's
forced document reload limit the effect; no cross-User rendering is proven.

Source: job/mailhtml-726 at 25acb01ed189f4429872a66a886081822c456229.
apps/web/src/lib/mail/readerCache.ts:98 exports four module caches.
The module has no session, auth, access or plugin invalidation listener.
The class has no clear method. getOrLoad at line 66 publishes late reads.
MailView.svelte:699 stores a snapshot during component destruction and does
not purge any cache. Keys include User ID; they do not enforce session end.

The local check imports that exact queued module with an EventTarget as the
window. It inserts a synthetic body, emits calternal:session-ended, and
checks the retained body. A second check emits the event while a read waits,
then completes that read. Both retain the synthetic body. Output:

CONFIRMED: queued Mail body remains after session-ended
CONFIRMED: queued Mail read publishes after session-ended

Fix: reuse #665 and #666 rather than add a separate cache lifecycle. Clear
all four caches on session, User, plugin and access changes. Fence late
completion and teardown writes with a generation. Keep the limits and User
keys. #555 covers the earlier cleanup work; this is a queued regression.

Test: session end clears body, thread, list and shell values and pending
reads. A delayed read or component teardown must not restore them. Verify
same-User reauthentication and a switch to another User.

Limits

This is not a browser exploit test or a dependency advisory scan. A code
trace does not prove a deployed browser result. No product fix or deployed acceptance check is claimed.

Coverage

Area Evidence and result
HTML sinks Enumerated production {@html}, innerHTML and srcdoc in apps/web and packages. Menu and slash glyphs use app constants. QR markup comes from qr, not interpolated URL text. TextView uses highlight.js output. No confirmed user-text HTML injection found by code trace. Library internals were not rebuilt or fuzzed.
Markdown and SVG packages/editor/src/markdown.ts constructs schema text/nodes. Note links use notes/paths.ts:122, which admits HTTP, HTTPS and mailto and refuses other schemes. files/user_bytes.rs:154 sends active types as text; SVG is an image only for an image destination. HTML/XML/script bytes have a sandbox CSP and nosniff.
Mail mail/html.rs:13 removes styles, active tags and attributes; links are extracted separately. MailView.svelte:547 has no script sandbox permission. mail/frame.ts:103 denies scripts/forms/network by default. Same-origin is used for parent height measurements. #726 must remove it before sender CSS support; its pinned branch still has the old sanitizer/frame model. Direct consented images and missing faithful rendering are already owned by #726.
CSP and nonces server/security.rs:121 enforces script hashes for embedded inline scripts; no nonce mechanism is used or needed for those fixed bytes. wire.rs:1497 installs the baseline middleware. Missing embedded frontend has a weaker fallback policy but returns no working SPA. frame-src 'none' and Mail srcdoc compatibility need the production Mail browser gate; no CSP weakening is proposed.
Clickjacking Baseline sets X-Frame-Options DENY and frame-ancestors none. File bytes get their own opaque-origin sandbox policy. No confirmed framing bypass found by code trace.
Messages No window message listener found. The inbox listens on the ServiceWorker container, not window. Worker and inbox links require an in-app path and reject double-slash and backslash forms. Pending push privacy is B1.
Redirects auth/routes.ts:13 parses return paths against a fixed origin and compares the result. OIDC destinations come from the server. Calendar attachments admit HTTP(S); Note navigation rejects other schemes. No confirmed open redirect found.
Browser storage Shared storage tags keys and values with User ID, captures the document owner, rejects changed-cookie reads and late writes. Session end clears the hint before listeners run. Cross-tab revision changes reload. HTTP thumbnails are B2; queued Mail cache retention is B4.
Revision cache #665 User/item/revision/variant tuple keys; bounded bodies and pending work; epoch fence; session/plugin/access invalidation listeners. Tests cover cache hits, 304 identity and cancellation. Missing access-event producers and refused-live-room fallback need an integrated access-revoke check before adoption for shared Notes. No new authorization bypass is claimed from that incomplete integration.
Snapshots #666 User/view/query tuple keys; hard limits; pending reservations fail after clear. Session/auth/plugin events clear memory. This branch adds a primitive, not shared-item adoption. Access-revoke wiring remains an adoption requirement.
Receipts #667 Intent arguments use shared User storage with row/byte caps. Controller captures its User, rejects late completion after disposal, and Mail preferences dispose on session end. No receipt credential is stored by the reviewed adapter.
Third-party origins App fonts and core scripts use the app origin. Provider-help links navigate only after a click. Unsplash requests use server appearance routes. External Note images are B3; Mail images after consent are covered by #726.

The sink inventory is a code review, not proof that all third-party parser
versions are free of defects. No packages were added or version changes made.
No full dependency advisory or licence audit was run.

Queued sources

Reviewed relevant security paths in these pinned sources:

  • round-7a: 2f4482ded066d9c5d9c59130377907f7fd2916c9.
  • #665: b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2.
  • #666: 253c2a00cade24a7f845a5e67f309093641b8850.
  • #667: 52d2b17f805072cd0304d7a05fe0534523cc7bc3.
  • #726/Mail layouts: 25acb01ed189f4429872a66a886081822c456229.
  • #663 design rules: e62249dedcc2c7d108e4432596d40aee6f5a4bc8.

Also scanned added browser sink/storage lines in the relevant round-7b
UI branches: writeonopen-661 04c4a651b, voicefiles-620 b7ef7a2ab,
calimg-589 421dd6373, blaze-settings bf3ad5f29, fix-499 242022301,
and kbdcaps-710 f5ade2d5b. New calimg direct storage lines are tests.
Settings preload checks same origin. No new finding came from this scan.
This is not a full review of each feature or of all backend branch changes.

Verification

Before final checks, the required fetch and merge of origin/dev returned:

Already up to date.

git diff --check and cargo fmt --check each exited 0 with no output.
Rust clippy/test: not run. No Rust crate or contract changed. This audit uses
the brief's minimal-build rule. No server build or adversarial server run was
made, so runtime severity has the limits stated in each finding.

bun run check exited 1. Output verbatim:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
node:internal/modules/package_json_reader:314
  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from /home/kayg/Developer/calternal-wt/sec-browser/apps/web/scripts/check-user-storage.mjs
    at Object.getPackageJSONURL (node:internal/modules/package_json_reader:314:9)
    at packageResolve (node:internal/modules/esm/resolve:772:81)
    at moduleResolve (node:internal/modules/esm/resolve:859:18)
    at defaultResolve (node:internal/modules/esm/resolve:989:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:747:20)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:724:38)
    at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:320:38)
    at ModuleJob._link (node:internal/modules/esm/module_job:182:49) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v22.23.3
error: script "check" exited with code 1

bun run test exited 127. Output verbatim:

$ vitest run
/usr/bin/bash: line 1: vitest: command not found
error: script "test" exited with code 127

The worktree has no installed web dependencies. No install was done for a
report-only change. The synthetic cache check ran with Bun and no dependency
install. Its exact output is in B4. Its script and pinned source are in
artifacts/, which is not committed.

Cleanup: cargo clean exited 0. Output verbatim:

     Removed 1 file, 356B total

No web build output existed after the checks. No benchmark is required:
this job adds no user-facing feature, route or background job.

Decisions and gaps

  • Follow the direct browser audit brief while keeping #663 as the parent.
  • Use queued #663 §58 for the access/cache bar; do not overwrite DESIGN.
  • File independent push, HTTP cache, Note privacy and queued Mail lifecycle
    issues. Keep existing Mail proxy work on #726. Do not close any issue.
  • Use code traces and one benign lifecycle check. Do not make product edits.
  • UX gaps closed: none; no UI changed. UX gaps left: #726 still owns the
    faithful Mail reader and silent image protection.
  • Known gaps: real-server/browser verification, full dependency advisories,
    integrated Share/plugin-revoke tests for queued caches, and web gates with
    dependencies installed. No macOS screenshots are needed for this report.

Browser semantics were checked against the HTTP cache reference
and PushSubscription unsubscribe reference.
HTTP cache retention/revalidation effects in B2 are reasoned from those
semantics and the response policy, not from a measured browser run.

sec-browser final report Built: documentation-only browser security audit; no product edits. Files: audit-findings.md. Branch: job/sec-browser. Head SHA: 53b430da8cbfb40dfceaf560e2248d6ed82de2ed Commits: c89c1ba22 (initial findings), 53b430da8 (coverage, issue links, verification). Findings: #759 and #765 marked BLOCKER; #766 and #767 are privacy/cleanup follow-ups. No push or deploy. Required origin/dev merge returned Already up to date. # Browser security audit This report records the sec-browser job for #663. It is a read-only review. It changes no product code. Dates use the environment date, 2026-10-02. ## Source and rules - Base and audited `origin/dev`: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. - Rules read: `CLAUDE.md`, `CONTEXT.md`, and `docs/DESIGN.md`. - #663 is the performance umbrella. The direct job brief defines this audit. - DESIGN on dev ends at §57. On round-7a, §58 describes agent discovery. `job/instant-663` adds the intended performance and access rules in §58. This review uses §§21, 45, 54, 57 and that queued §58. - Queue source: `~/.local/state/codex-jobs/calternal/paused-queue.txt`. - Evidence below is a code trace. No hostile payloads, external attacks, production access, push, deploy or product edits are part of this job. ## Filed findings ### B1 — Push survives session end Status: confirmed by code trace; filed as [#759](https://git.kayg.org/kayg/calternal/issues/759). Merge class: BLOCKER, private data can reach a signed-out Installation. `apps/web/src/routes/+layout.svelte:670` deletes the browser session, calls `endUserSession`, and goes to sign-in. It does not call `disablePush`. `apps/web/src/lib/userStorage.ts:205` removes the hint and User stores, but does not remove the browser push subscription. The only unsubscribe path is `apps/web/src/lib/notifications/push.ts:196`, for explicit disable. `crates/plugins/notifications/src/store.rs:586` binds a subscription to User and Installation, without a session. `pending_pushes` at line 665 joins that row without checking a live session. `push.rs:212` sends the stored title, body and link. `reminders.rs:61` puts an Event summary in the body; line 162 puts a Log title there. `apps/web/src/service-worker.ts:55` shows it without checking the current User. A later User need not enable push to receive it. Fix: revoke this Installation's delivery authority at session end. Bind push authority to a revocable session or Installation generation on the server. Unsubscribe locally even when the session has already expired. Clear shown notifications and reject delayed payloads for an ended User. Keep all other Installations working. Test: use two synthetic Users and a fake push transport. End A's session, then enqueue an A reminder. Assert no delivery to this Installation. Sign in B with push off and assert no A system notification. Check session expiry, remote revoke, pending delivery and a second live A Installation. ### B2 — Private thumbnails enter unmanaged HTTP cache Status: confirmed policy gap by code trace; duplicate search complete; filed as [#765](https://git.kayg.org/kayg/calternal/issues/765). Merge class: BLOCKER for Share revocation. No claim of cross-User cookie-cache reuse is made. `crates/plugins/files/src/thumbnails.rs:736` permits versioned thumbnails with `private, max-age=31536000, immutable` on both 200 and 304 responses. It varies on Cookie, which separates changed cookies. That does not cause revalidation when a Share is revoked with the same cookie. The route checks Share access before serving, but a fresh browser cache can skip the route. `apps/web/src/lib/appearance/background.svelte.ts:432` uses this variant. `userStorage.clearUser` deletes Web Storage, IndexedDB and Cache Storage, not the browser HTTP cache. Private photo bytes can remain after sign-out. Round-7a retains the same policy. #449 covers physical server thumbnail separation, not this browser response policy. Fix: prevent private bytes from entering unmanaged HTTP cache. Use no-store and a bounded User cache that cleanup can delete. If HTTP revalidation is kept for another use, require it on every use and check current access before 304. Cookie variance alone is not a revocation mechanism. Test: assert the versioned response cannot be reused without an access check. Use benign synthetic image fixtures for session cleanup and Share revoke. ### B3 — Notes load external images in the browser Status: confirmed by code trace; filed as [#766](https://git.kayg.org/kayg/calternal/issues/766). Merge class: privacy follow-up; no script execution claim. `apps/web/src/lib/notes/editorHost.ts:71` passes external HTTP(S) images to the browser. `NoteImageView.svelte:39` uses the result as an image source. `crates/calternal-server/src/security.rs:121` permits all HTTPS image origins. Opening an imported Note can disclose IP, request time and browser metadata to an image origin. The User does not need to click a link. Mail's consent-only direct image loads are already owned by #726; do not create a second Mail image-proxy issue. Fix: reuse the server image-proxy policy from #726 for Notes. Bound type, size and time; check public destinations and redirect hops; strip cookies and Referer. Make protection silent, as the owner requires. Test: with a benign synthetic remote image, assert no external browser request and no outbound cookies or Referer. Keep local attachments working. ### B4 — Queued Mail caches do not clear at session end Status: confirmed with a benign lifecycle check; filed as [#767](https://git.kayg.org/kayg/calternal/issues/767). Merge class: cleanup regression. User-keyed cache identity and the shell's forced document reload limit the effect; no cross-User rendering is proven. Source: `job/mailhtml-726` at `25acb01ed189f4429872a66a886081822c456229`. `apps/web/src/lib/mail/readerCache.ts:98` exports four module caches. The module has no session, auth, access or plugin invalidation listener. The class has no clear method. `getOrLoad` at line 66 publishes late reads. `MailView.svelte:699` stores a snapshot during component destruction and does not purge any cache. Keys include User ID; they do not enforce session end. The local check imports that exact queued module with an EventTarget as the window. It inserts a synthetic body, emits `calternal:session-ended`, and checks the retained body. A second check emits the event while a read waits, then completes that read. Both retain the synthetic body. Output: ```text CONFIRMED: queued Mail body remains after session-ended CONFIRMED: queued Mail read publishes after session-ended ``` Fix: reuse #665 and #666 rather than add a separate cache lifecycle. Clear all four caches on session, User, plugin and access changes. Fence late completion and teardown writes with a generation. Keep the limits and User keys. #555 covers the earlier cleanup work; this is a queued regression. Test: session end clears body, thread, list and shell values and pending reads. A delayed read or component teardown must not restore them. Verify same-User reauthentication and a switch to another User. ## Limits This is not a browser exploit test or a dependency advisory scan. A code trace does not prove a deployed browser result. No product fix or deployed acceptance check is claimed. ## Coverage | Area | Evidence and result | | --- | --- | | HTML sinks | Enumerated production `{@html}`, `innerHTML` and `srcdoc` in apps/web and packages. Menu and slash glyphs use app constants. QR markup comes from `qr`, not interpolated URL text. TextView uses highlight.js output. No confirmed user-text HTML injection found by code trace. Library internals were not rebuilt or fuzzed. | | Markdown and SVG | `packages/editor/src/markdown.ts` constructs schema text/nodes. Note links use `notes/paths.ts:122`, which admits HTTP, HTTPS and mailto and refuses other schemes. `files/user_bytes.rs:154` sends active types as text; SVG is an image only for an image destination. HTML/XML/script bytes have a sandbox CSP and nosniff. | | Mail | `mail/html.rs:13` removes styles, active tags and attributes; links are extracted separately. `MailView.svelte:547` has no script sandbox permission. `mail/frame.ts:103` denies scripts/forms/network by default. Same-origin is used for parent height measurements. #726 must remove it before sender CSS support; its pinned branch still has the old sanitizer/frame model. Direct consented images and missing faithful rendering are already owned by #726. | | CSP and nonces | `server/security.rs:121` enforces script hashes for embedded inline scripts; no nonce mechanism is used or needed for those fixed bytes. `wire.rs:1497` installs the baseline middleware. Missing embedded frontend has a weaker fallback policy but returns no working SPA. `frame-src 'none'` and Mail srcdoc compatibility need the production Mail browser gate; no CSP weakening is proposed. | | Clickjacking | Baseline sets X-Frame-Options DENY and frame-ancestors none. File bytes get their own opaque-origin sandbox policy. No confirmed framing bypass found by code trace. | | Messages | No window message listener found. The inbox listens on the ServiceWorker container, not window. Worker and inbox links require an in-app path and reject double-slash and backslash forms. Pending push privacy is B1. | | Redirects | `auth/routes.ts:13` parses return paths against a fixed origin and compares the result. OIDC destinations come from the server. Calendar attachments admit HTTP(S); Note navigation rejects other schemes. No confirmed open redirect found. | | Browser storage | Shared storage tags keys and values with User ID, captures the document owner, rejects changed-cookie reads and late writes. Session end clears the hint before listeners run. Cross-tab revision changes reload. HTTP thumbnails are B2; queued Mail cache retention is B4. | | Revision cache #665 | User/item/revision/variant tuple keys; bounded bodies and pending work; epoch fence; session/plugin/access invalidation listeners. Tests cover cache hits, 304 identity and cancellation. Missing access-event producers and refused-live-room fallback need an integrated access-revoke check before adoption for shared Notes. No new authorization bypass is claimed from that incomplete integration. | | Snapshots #666 | User/view/query tuple keys; hard limits; pending reservations fail after clear. Session/auth/plugin events clear memory. This branch adds a primitive, not shared-item adoption. Access-revoke wiring remains an adoption requirement. | | Receipts #667 | Intent arguments use shared User storage with row/byte caps. Controller captures its User, rejects late completion after disposal, and Mail preferences dispose on session end. No receipt credential is stored by the reviewed adapter. | | Third-party origins | App fonts and core scripts use the app origin. Provider-help links navigate only after a click. Unsplash requests use server appearance routes. External Note images are B3; Mail images after consent are covered by #726. | The sink inventory is a code review, not proof that all third-party parser versions are free of defects. No packages were added or version changes made. No full dependency advisory or licence audit was run. ## Queued sources Reviewed relevant security paths in these pinned sources: - round-7a: `2f4482ded066d9c5d9c59130377907f7fd2916c9`. - #665: `b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2`. - #666: `253c2a00cade24a7f845a5e67f309093641b8850`. - #667: `52d2b17f805072cd0304d7a05fe0534523cc7bc3`. - #726/Mail layouts: `25acb01ed189f4429872a66a886081822c456229`. - #663 design rules: `e62249dedcc2c7d108e4432596d40aee6f5a4bc8`. Also scanned added browser sink/storage lines in the relevant round-7b UI branches: writeonopen-661 `04c4a651b`, voicefiles-620 `b7ef7a2ab`, calimg-589 `421dd6373`, blaze-settings `bf3ad5f29`, fix-499 `242022301`, and kbdcaps-710 `f5ade2d5b`. New calimg direct storage lines are tests. Settings preload checks same origin. No new finding came from this scan. This is not a full review of each feature or of all backend branch changes. ## Verification Before final checks, the required fetch and merge of `origin/dev` returned: ```text Already up to date. ``` `git diff --check` and `cargo fmt --check` each exited 0 with no output. Rust clippy/test: not run. No Rust crate or contract changed. This audit uses the brief's minimal-build rule. No server build or adversarial server run was made, so runtime severity has the limits stated in each finding. `bun run check` exited 1. Output verbatim: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json node:internal/modules/package_json_reader:314 throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null); ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from /home/kayg/Developer/calternal-wt/sec-browser/apps/web/scripts/check-user-storage.mjs at Object.getPackageJSONURL (node:internal/modules/package_json_reader:314:9) at packageResolve (node:internal/modules/esm/resolve:772:81) at moduleResolve (node:internal/modules/esm/resolve:859:18) at defaultResolve (node:internal/modules/esm/resolve:989:11) at #cachedDefaultResolve (node:internal/modules/esm/loader:747:20) at ModuleLoader.resolve (node:internal/modules/esm/loader:724:38) at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:320:38) at ModuleJob._link (node:internal/modules/esm/module_job:182:49) { code: 'ERR_MODULE_NOT_FOUND' } Node.js v22.23.3 error: script "check" exited with code 1 ``` `bun run test` exited 127. Output verbatim: ```text $ vitest run /usr/bin/bash: line 1: vitest: command not found error: script "test" exited with code 127 ``` The worktree has no installed web dependencies. No install was done for a report-only change. The synthetic cache check ran with Bun and no dependency install. Its exact output is in B4. Its script and pinned source are in `artifacts/`, which is not committed. Cleanup: `cargo clean` exited 0. Output verbatim: ```text Removed 1 file, 356B total ``` No web build output existed after the checks. No benchmark is required: this job adds no user-facing feature, route or background job. ## Decisions and gaps - Follow the direct browser audit brief while keeping #663 as the parent. - Use queued #663 §58 for the access/cache bar; do not overwrite DESIGN. - File independent push, HTTP cache, Note privacy and queued Mail lifecycle issues. Keep existing Mail proxy work on #726. Do not close any issue. - Use code traces and one benign lifecycle check. Do not make product edits. - UX gaps closed: none; no UI changed. UX gaps left: #726 still owns the faithful Mail reader and silent image protection. - Known gaps: real-server/browser verification, full dependency advisories, integrated Share/plugin-revoke tests for queued caches, and web gates with dependencies installed. No macOS screenshots are needed for this report. Browser semantics were checked against the [HTTP cache reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) and [PushSubscription unsubscribe reference](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/unsubscribe). HTTP cache retention/revalidation effects in B2 are reasoned from those semantics and the response policy, not from a measured browser run.
Author
Owner

Completed perf-guards (specifications only).

Branch: job/perf-guards. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
Head: e6f52f78cd8a78003de35c53277c787662fe78fe. Three atomic documentation commits. No product edits, guard implementation, push or deploy. Final fetch and merge origin/dev ran once: Already up to date.

Built: eight self-contained mechanical guard specifications, filed after duplicate search:

  • #791 coverage registry and exact expiring exceptions.
  • #792 bounded list SQL, response rows/bytes and signed keyset contracts.
  • #793 interactive read capability checks (file/parser/provider IO).
  • #794 paint barriers, repeated layout reads, keyboard selection and local blur.
  • #795 revision/snapshot/mutation/stream adoption contracts.
  • #796 bounded rendering and strict warm blaze result checks.
  • #797 deterministic shell/route bundle budgets.
  • #798 required route-open profiles and budget report validation.

Files: audit-findings.md only. It contains source evidence, pinned queued branch revisions, duplicate search notes, every filed specification and validation. Existing product fixes remain #665–#704 and #497; no duplicates filed for them. New finding: queued Mail blaze CLI computes incomplete/mismatch counts but returns without asserting them; #796 extends #641. This is a harness finding, not a claim that current Mail has incomplete frames.

Gate output verbatim:

Already up to date.
PASS: 8 guard specifications, issue links, exception contracts and origin/dev source anchors.
PASS: only audit-findings.md differs from origin/dev; no product or guard code changed.
#791: read-back sections verified
#792: read-back sections verified
#793: read-back sections verified
#794: read-back sections verified
#795: read-back sections verified
#796: read-back sections verified
#797: read-back sections verified
#798: read-back sections verified
PASS: committed audit contains all 8 specifications; worktree is clean.

git diff --check origin/dev HEAD exited 0 with no output. Rust fmt/clippy/test and bun check/test were not run: documentation only, no changed crate or web code. No API changed; no adversarial server round or visual build needed. No dependency selected; no version lookup needed.

Cleanup output verbatim:

     Removed 1 file, 356B total

Cargo clean used the preset job target and required environment; web build output removed if present.

Known gaps: all guard implementation, surface adoption and new production measurements remain for the filed/linked issues. Source analysis must flag unresolved constructs instead of claiming complete language inference. Current violations require exact frozen debt entries; the specs do not assert current dev passes. No source pattern here is labelled a measured slowdown or a new security BLOCKER.

Decisions: (1) deterministic architecture/bounds/coverage/build checks fail required CI; wall-clock perf-budget runner failures stay periodic and non-blocking under CLAUDE.md/Instant interactions. (2) Proposed render threshold 100, initial JS budget interpreted as 200,000 gzip bytes, timing threshold applied to p95 while retaining median/max. (3) Per-representation byte caps and route CSS/lazy-group caps need an implementation contract or fresh baseline, not invented numbers. (4) One registry and exact-hash expiring allow-list for all guards, no wildcard suppression. (5) Cite #663 and Instant interactions title: its §58 collides with round-7a Agent discovery §58; no product/design numbering edit in this job.

UX gaps closed: none (no UI change). UX gaps left: outside this specification-only job. Issue left open.

Completed perf-guards (specifications only). Branch: `job/perf-guards`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Head: `e6f52f78cd8a78003de35c53277c787662fe78fe`. Three atomic documentation commits. No product edits, guard implementation, push or deploy. Final fetch and merge origin/dev ran once: `Already up to date.` Built: eight self-contained mechanical guard specifications, filed after duplicate search: - #791 coverage registry and exact expiring exceptions. - #792 bounded list SQL, response rows/bytes and signed keyset contracts. - #793 interactive read capability checks (file/parser/provider IO). - #794 paint barriers, repeated layout reads, keyboard selection and local blur. - #795 revision/snapshot/mutation/stream adoption contracts. - #796 bounded rendering and strict warm blaze result checks. - #797 deterministic shell/route bundle budgets. - #798 required route-open profiles and budget report validation. Files: `audit-findings.md` only. It contains source evidence, pinned queued branch revisions, duplicate search notes, every filed specification and validation. Existing product fixes remain #665–#704 and #497; no duplicates filed for them. New finding: queued Mail blaze CLI computes incomplete/mismatch counts but returns without asserting them; #796 extends #641. This is a harness finding, not a claim that current Mail has incomplete frames. Gate output verbatim: ```text Already up to date. PASS: 8 guard specifications, issue links, exception contracts and origin/dev source anchors. PASS: only audit-findings.md differs from origin/dev; no product or guard code changed. #791: read-back sections verified #792: read-back sections verified #793: read-back sections verified #794: read-back sections verified #795: read-back sections verified #796: read-back sections verified #797: read-back sections verified #798: read-back sections verified PASS: committed audit contains all 8 specifications; worktree is clean. ``` `git diff --check origin/dev HEAD` exited 0 with no output. Rust fmt/clippy/test and bun check/test were not run: documentation only, no changed crate or web code. No API changed; no adversarial server round or visual build needed. No dependency selected; no version lookup needed. Cleanup output verbatim: ```text Removed 1 file, 356B total ``` Cargo clean used the preset job target and required environment; web build output removed if present. Known gaps: all guard implementation, surface adoption and new production measurements remain for the filed/linked issues. Source analysis must flag unresolved constructs instead of claiming complete language inference. Current violations require exact frozen debt entries; the specs do not assert current dev passes. No source pattern here is labelled a measured slowdown or a new security BLOCKER. Decisions: (1) deterministic architecture/bounds/coverage/build checks fail required CI; wall-clock perf-budget runner failures stay periodic and non-blocking under CLAUDE.md/Instant interactions. (2) Proposed render threshold 100, initial JS budget interpreted as 200,000 gzip bytes, timing threshold applied to p95 while retaining median/max. (3) Per-representation byte caps and route CSS/lazy-group caps need an implementation contract or fresh baseline, not invented numbers. (4) One registry and exact-hash expiring allow-list for all guards, no wildcard suppression. (5) Cite #663 and Instant interactions title: its §58 collides with round-7a Agent discovery §58; no product/design numbering edit in this job. UX gaps closed: none (no UI change). UX gaps left: outside this specification-only job. Issue left open.
Author
Owner

Completed the sec-sharing source audit on job/sec-sharing.

Head: f716808ae89538af2fcce9bceab73a1b58a5e537.
Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
Commits: fcc975faa (findings), f716808ae (coverage and proof limits).
File built: audit-findings.md. No product, dependency or lockfile changes.

Findings:

  • #755 BLOCKER: public download authorization precedes the namespace lock wait; the open and counter do not bind to the current item/grant. Normal deletion removes link rows; move indexing retains and updates them. Impact is reasoned from source, not reproduced.
  • #756 BLOCKER: public link password work runs synchronously on async request workers without a shared work budget. The report specifies a blocking worker and bounded permits, including cancellation behavior. No DoS or latency claim was measured.

Coverage includes Share/Collaborate, public links, Photos, Note rooms, search, thumbnails, previews, file drop and Calendar feed tokens. Relevant round-7a and round-7b changes were checked. #707 and #479/#461 findings were not duplicated. The report includes source references, concrete fixes and defensive regression requirements.

Validation:

  • Final fetch/merge of origin/dev, verbatim output:
Already up to date.
  • cargo fmt --check: exit 0; stdout and stderr empty.
  • git diff --check: exit 0; stdout and stderr empty.
  • cargo clean: exit 0, verbatim output:
     Removed 1 file, 356B total
  • Clippy, crate tests and web gates were not run: no crate or web code changed and this job requires minimal builds. No web build output was created. Worktree is clean. No push or deployment.

Known gaps: both new findings require fixes and controlled regression tests; no runtime vulnerability reproduction or live adversarial round was performed. No production or Apple-client checks. #707 historical extra-item ownership remains unknown. Shared Note opening, previews and delayed persistence still need controlled checks. Groups/invite links/internal expiry and the §54 changes remain in #479/#461.

Decisions: the supplied #663/§58 references describe performance/agent discovery, not security. Used the explicit job security scope and §§22, 26, 48 and 54. Followed the specific no-product-edits audit rule. Kept source evidence separate from runtime proof and filed authorization/resource-control defects as blockers under the owner rule.

UX gaps closed: none (no UI changes). UX gaps left: §54 implementation remains in #479/#461. No visual verdict.

Completed the sec-sharing source audit on `job/sec-sharing`. Head: `f716808ae89538af2fcce9bceab73a1b58a5e537`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Commits: `fcc975faa` (findings), `f716808ae` (coverage and proof limits). File built: `audit-findings.md`. No product, dependency or lockfile changes. Findings: - #755 BLOCKER: public download authorization precedes the namespace lock wait; the open and counter do not bind to the current item/grant. Normal deletion removes link rows; move indexing retains and updates them. Impact is reasoned from source, not reproduced. - #756 BLOCKER: public link password work runs synchronously on async request workers without a shared work budget. The report specifies a blocking worker and bounded permits, including cancellation behavior. No DoS or latency claim was measured. Coverage includes Share/Collaborate, public links, Photos, Note rooms, search, thumbnails, previews, file drop and Calendar feed tokens. Relevant round-7a and round-7b changes were checked. #707 and #479/#461 findings were not duplicated. The report includes source references, concrete fixes and defensive regression requirements. Validation: - Final fetch/merge of `origin/dev`, verbatim output: ```text Already up to date. ``` - `cargo fmt --check`: exit 0; stdout and stderr empty. - `git diff --check`: exit 0; stdout and stderr empty. - `cargo clean`: exit 0, verbatim output: ```text Removed 1 file, 356B total ``` - Clippy, crate tests and web gates were not run: no crate or web code changed and this job requires minimal builds. No web build output was created. Worktree is clean. No push or deployment. Known gaps: both new findings require fixes and controlled regression tests; no runtime vulnerability reproduction or live adversarial round was performed. No production or Apple-client checks. #707 historical extra-item ownership remains unknown. Shared Note opening, previews and delayed persistence still need controlled checks. Groups/invite links/internal expiry and the §54 changes remain in #479/#461. Decisions: the supplied #663/§58 references describe performance/agent discovery, not security. Used the explicit job security scope and §§22, 26, 48 and 54. Followed the specific no-product-edits audit rule. Kept source evidence separate from runtime proof and filed authorization/resource-control defects as blockers under the owner rule. UX gaps closed: none (no UI changes). UX gaps left: §54 implementation remains in #479/#461. No visual verdict.
Author
Owner

Supply-chain evidence is recorded in the first audit commit a27858ad8. Filed #810 (six Bun advisories in four dependency groups), #811 (Rust lru/rsa advisory matches and five unmaintained dependencies), #812 (no repeatable advisory/licence policy), and #813 (mutable action, image and OS-package inputs). These are non-blocking maintenance findings: no reachable product security hole was proved. Production CSP mitigates the flagged PDF.js issue; the affected Tiptap Markdown helpers are not used; sharp is used only by icon scripts. Root-lock lru is used with integer keys, and no production RSA decryption path was found. 743 npm release integrity values match registry metadata. All seven model assets match upstream SHA-256 values or LFS hash metadata and declared sizes. Remaining work: finish round-7a/7b comparison, final report checks, required origin/dev refresh and cleanup.

Supply-chain evidence is recorded in the first audit commit a27858ad8. Filed #810 (six Bun advisories in four dependency groups), #811 (Rust lru/rsa advisory matches and five unmaintained dependencies), #812 (no repeatable advisory/licence policy), and #813 (mutable action, image and OS-package inputs). These are non-blocking maintenance findings: no reachable product security hole was proved. Production CSP mitigates the flagged PDF.js issue; the affected Tiptap Markdown helpers are not used; sharp is used only by icon scripts. Root-lock lru is used with integer keys, and no production RSA decryption path was found. 743 npm release integrity values match registry metadata. All seven model assets match upstream SHA-256 values or LFS hash metadata and declared sizes. Remaining work: finish round-7a/7b comparison, final report checks, required origin/dev refresh and cleanup.
Author
Owner

Security audit finished on branch job/sec-mcp-scopes.

Head: 6f72d9e5ccca96beee7956a1b50f5de3f7a58149.
Base/final origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
Three atomic documentation commits: ceaefa119, c7bfe6a7f, 6f72d9e5c.
Only tracked file changed: audit-findings.md (findings, all 333 operations, source boundary review, Events/queued changes, decisions and verification).

Findings filed after duplicate searches:

  • BLOCKER #739: data-only Agent authority can permanently empty Trash; require account authority before filesystem/Index changes.
  • BLOCKER #743: data-only Agent authority can create and manage public Calendar feed grants; require account authority for publication and capability management.
  • #746: raw MCP results and public agent guidance do not state the untrusted-content boundary; add shared result/provenance handling and explicit guidance. No demonstrated model exploit or authorization bypass is claimed.
  • #774: 26 registry operations omit account requirements already enforced by their routes (Files Shares/links, Version listing, Notifications, Tab order and User Background Work). Existing denials must remain.

Coverage: origin/dev has 333 operations and 315 generated tools. Reviewed MCP original-credential dispatch, protocol read/write checks, User boundaries, admin/freshness guards, dangerous actions, confirmation behavior and untrusted content. Reviewed merge-round-7a at 2f4482ded066d9c5d9c59130377907f7fd2916c9, including MCP Events and public agent docs; checked relevant round-7b branches from paused-queue. No additional Events scope violation was confirmed from reviewed source. This is source review, not certification of every live operation.

Final fetch/merge output, verbatim:

Already up to date.

Gate output, verbatim:
cargo fmt --check and git diff --check exited 0 with no output.

Action registry: 333 operations, 315 generated tools
Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps
...........
----------------------------------------------------------------------
Ran 11 tests in 0.827s

OK
.......
----------------------------------------------------------------------
Ran 7 tests in 0.275s

OK

Commands: python3 scripts/action_registry.py --check; python3 scripts/parity_matrix.py --check; python3 -m unittest discover -s scripts -p test_action_registry.py; python3 -m unittest discover -s tests/adversarial -p test_xuser_classification.py.

No crate or web source changed; clippy, crate runtime tests and web gates were not run. No product build, benchmark, live webhook or adversarial attack was run. No dependency changes. No push or deployment. Final merge from origin/dev was already up to date. Cleanup output, verbatim:

     Removed 1 file, 356B total
No web build output to remove

Known gaps: F1/F2 fixes and their regression tests remain; full live cross-User/scope/tool matrices remain under #331/#472/#484. Existing smoke counts of 288 tools do not prove the current 315. Events callback/revocation timing and eventual combined queued merge remain unverified. Public edit tools must be reconciled with DESIGN §54 by the existing sharing jobs. No product fixes are claimed.

Decisions: followed the explicit security audit despite this issue actually tracking performance; base DESIGN lacks §58, and merge-round-7a §58 is agent discovery (#630). Used §§21/27/41/48/55 as the available security bar. Labels are defense in depth, not authorization. No new confirmation policy was invented. Connected Account credential management currently uses data authority; stronger scope needs an owner decision, not an audit-only product change.

UX gaps closed: none (no UI changes). UX gaps left: not assessed.

Security audit finished on branch `job/sec-mcp-scopes`. Head: `6f72d9e5ccca96beee7956a1b50f5de3f7a58149`. Base/final origin/dev: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Three atomic documentation commits: `ceaefa119`, `c7bfe6a7f`, `6f72d9e5c`. Only tracked file changed: `audit-findings.md` (findings, all 333 operations, source boundary review, Events/queued changes, decisions and verification). Findings filed after duplicate searches: - BLOCKER #739: data-only Agent authority can permanently empty Trash; require account authority before filesystem/Index changes. - BLOCKER #743: data-only Agent authority can create and manage public Calendar feed grants; require account authority for publication and capability management. - #746: raw MCP results and public agent guidance do not state the untrusted-content boundary; add shared result/provenance handling and explicit guidance. No demonstrated model exploit or authorization bypass is claimed. - #774: 26 registry operations omit account requirements already enforced by their routes (Files Shares/links, Version listing, Notifications, Tab order and User Background Work). Existing denials must remain. Coverage: origin/dev has 333 operations and 315 generated tools. Reviewed MCP original-credential dispatch, protocol read/write checks, User boundaries, admin/freshness guards, dangerous actions, confirmation behavior and untrusted content. Reviewed merge-round-7a at `2f4482ded066d9c5d9c59130377907f7fd2916c9`, including MCP Events and public agent docs; checked relevant round-7b branches from paused-queue. No additional Events scope violation was confirmed from reviewed source. This is source review, not certification of every live operation. Final fetch/merge output, verbatim: ```text Already up to date. ``` Gate output, verbatim: `cargo fmt --check` and `git diff --check` exited 0 with no output. ```text Action registry: 333 operations, 315 generated tools Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps ........... ---------------------------------------------------------------------- Ran 11 tests in 0.827s OK ....... ---------------------------------------------------------------------- Ran 7 tests in 0.275s OK ``` Commands: `python3 scripts/action_registry.py --check`; `python3 scripts/parity_matrix.py --check`; `python3 -m unittest discover -s scripts -p test_action_registry.py`; `python3 -m unittest discover -s tests/adversarial -p test_xuser_classification.py`. No crate or web source changed; clippy, crate runtime tests and web gates were not run. No product build, benchmark, live webhook or adversarial attack was run. No dependency changes. No push or deployment. Final merge from origin/dev was already up to date. Cleanup output, verbatim: ```text Removed 1 file, 356B total No web build output to remove ``` Known gaps: F1/F2 fixes and their regression tests remain; full live cross-User/scope/tool matrices remain under #331/#472/#484. Existing smoke counts of 288 tools do not prove the current 315. Events callback/revocation timing and eventual combined queued merge remain unverified. Public edit tools must be reconciled with DESIGN §54 by the existing sharing jobs. No product fixes are claimed. Decisions: followed the explicit security audit despite this issue actually tracking performance; base DESIGN lacks §58, and merge-round-7a §58 is agent discovery (#630). Used §§21/27/41/48/55 as the available security bar. Labels are defense in depth, not authorization. No new confirmation policy was invented. Connected Account credential management currently uses data authority; stronger scope needs an owner decision, not an audit-only product change. UX gaps closed: none (no UI changes). UX gaps left: not assessed.
Author
Owner

Sync audit findings recorded in audit-findings.md: #753 (Mail listener slots and 300 s gaps), #757 (delta/expunge job bounds), #769 (external Note hint reads all loaded rooms), #778 (one timer task per dirty frame), #800 (WebDAV reads on global mutation lock). Duplicate checks caught concurrent #761 (watcher queue; reused) and #763 (Mail slots; cross-linked #753). Added source/fix/test evidence to #573, #679 and #668.

Scratch Index result: the production Reminder delta query uses its (User, seq) index, but 10,000 retained changes for one Task fetch 10,000 rows before one UID remains. No VM timing, baseline ratio, runtime crash or security hole claimed. Audit is read-only except documentation and issue tracking. Final source review and documentation gates remain.

Sync audit findings recorded in audit-findings.md: #753 (Mail listener slots and 300 s gaps), #757 (delta/expunge job bounds), #769 (external Note hint reads all loaded rooms), #778 (one timer task per dirty frame), #800 (WebDAV reads on global mutation lock). Duplicate checks caught concurrent #761 (watcher queue; reused) and #763 (Mail slots; cross-linked #753). Added source/fix/test evidence to #573, #679 and #668. Scratch Index result: the production Reminder delta query uses its (User, seq) index, but 10,000 retained changes for one Task fetch 10,000 rows before one UID remains. No VM timing, baseline ratio, runtime crash or security hole claimed. Audit is read-only except documentation and issue tracking. Final source review and documentation gates remain.
Author
Owner

sec-fs source audit finished on job/sec-fs.

Head: 424a0e48bddeca0ebc7720f54864b9f44696e258. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Three atomic documentation commits: b2d724c41 (scope), 58a3b19f5 (findings), 424a0e48b (coverage and limits).

Built and files

Committed only audit-findings.md: evidence matrix, five findings, pinned round-7a/7b references, duplicate checks, concrete fixes and defensive test requirements. Issue bodies and gate logs remain under gitignored artifacts/sec-fs/. No product or test-expectation changes.

Findings

  • BLOCKER #779: active HLS output and large media disk snapshots lack disk-space reservations. Output eviction occurs after transcode completion.
  • BLOCKER #781: PDF search extraction has no hard child memory budget; decoded streams and page text allocate before final output caps. Exact pinned lopdf 0.42.0 source was verified on the web.
  • BLOCKER #801: file-drop XMP Sidecar authorization is checked at creation, not against the final destination under the installation lock.
  • Follow-up #802: pre-journal named temporary files can survive cancellation or I/O failure and remain charged to Home quota.
  • Existing #621 extended with restore evidence: restore omits the shared NFC/full-case-fold sibling conflict check.

No duplicate was filed for #501: immutable media snapshots are already pending in round 7a. Dedup timing isolation remains on #454.

Gates, verbatim captured output

The final required origin sync returned Already up to date.. The final whitespace check also exited 0 after the report edits. Captured command output and exit codes:

$ git diff --check
exit=0
$ cargo fmt --check
exit=0
$ cargo clean
     Removed 1 file, 356B total
exit=0

Both git diff --check and cargo fmt --check produced no diagnostic output. No Rust crate or web package changed, so per-crate clippy/test and bun gates are not applicable. No build or new dependency was needed. cargo clean used the preset job target, and web build output was removed. Worktree is clean. No push or deployment occurred.

Known gaps

These are source-confirmed missing boundaries, with reasoned impact. No live-server vulnerability reproduction, disk-full incident, OOM event or unauthorized metadata change was reproduced. No worst-case EXIF/track-parser allocation measurement, pending-branch production build or sync collision run was made. Fixes and regressions remain on the linked issues. No performance number is claimed; the perf VM was not needed for a missing-boundary finding.

Decisions

The explicit filesystem brief governed despite #663 being a performance issue. DESIGN on this base ends at §57; pending §58 from job/instant-663 is also a performance section. Existing security rules in §2, §5, §22, §26 and §39 remain the audit bar. Resource and authorization boundary gaps were filed as blockers; temporary cleanup remains a reliability follow-up without a proven outage. Restore extends #621 rather than creating another twin-prevention issue. Product edits were excluded by the job brief.

UX gaps closed: not applicable; no UI changed. UX gaps left: not assessed in this source audit.

sec-fs source audit finished on `job/sec-fs`. Head: `424a0e48bddeca0ebc7720f54864b9f44696e258`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Three atomic documentation commits: b2d724c41 (scope), 58a3b19f5 (findings), 424a0e48b (coverage and limits). ## Built and files Committed only `audit-findings.md`: evidence matrix, five findings, pinned round-7a/7b references, duplicate checks, concrete fixes and defensive test requirements. Issue bodies and gate logs remain under gitignored `artifacts/sec-fs/`. No product or test-expectation changes. ## Findings - BLOCKER #779: active HLS output and large media disk snapshots lack disk-space reservations. Output eviction occurs after transcode completion. - BLOCKER #781: PDF search extraction has no hard child memory budget; decoded streams and page text allocate before final output caps. Exact pinned lopdf 0.42.0 source was verified on the web. - BLOCKER #801: file-drop XMP Sidecar authorization is checked at creation, not against the final destination under the installation lock. - Follow-up #802: pre-journal named temporary files can survive cancellation or I/O failure and remain charged to Home quota. - Existing #621 extended with restore evidence: restore omits the shared NFC/full-case-fold sibling conflict check. No duplicate was filed for #501: immutable media snapshots are already pending in round 7a. Dedup timing isolation remains on #454. ## Gates, verbatim captured output The final required origin sync returned `Already up to date.`. The final whitespace check also exited 0 after the report edits. Captured command output and exit codes: ```text $ git diff --check exit=0 $ cargo fmt --check exit=0 $ cargo clean Removed 1 file, 356B total exit=0 ``` Both `git diff --check` and `cargo fmt --check` produced no diagnostic output. No Rust crate or web package changed, so per-crate clippy/test and bun gates are not applicable. No build or new dependency was needed. `cargo clean` used the preset job target, and web build output was removed. Worktree is clean. No push or deployment occurred. ## Known gaps These are source-confirmed missing boundaries, with reasoned impact. No live-server vulnerability reproduction, disk-full incident, OOM event or unauthorized metadata change was reproduced. No worst-case EXIF/track-parser allocation measurement, pending-branch production build or sync collision run was made. Fixes and regressions remain on the linked issues. No performance number is claimed; the perf VM was not needed for a missing-boundary finding. ## Decisions The explicit filesystem brief governed despite #663 being a performance issue. DESIGN on this base ends at §57; pending §58 from job/instant-663 is also a performance section. Existing security rules in §2, §5, §22, §26 and §39 remain the audit bar. Resource and authorization boundary gaps were filed as blockers; temporary cleanup remains a reliability follow-up without a proven outage. Restore extends #621 rather than creating another twin-prevention issue. Product edits were excluded by the job brief. UX gaps closed: not applicable; no UI changed. UX gaps left: not assessed in this source audit.
Author
Owner

Finished perf-arch-client: browser runtime audit.

Branch: job/perf-arch-client
Base: c4a61e8cf0 (origin/dev)
Head: 93f0db7213
Commits: 0eed90202 (scope), efe200432 (ranked findings), 93f0db721 (verification).
Files: audit-findings.md only. Ignored evidence/probe files remain under artifacts/perf-arch-client/. No product edits, pushes or deploys. The required fetch and origin/dev integration returned “Already up to date.”

Built

  • Ranked runtime findings for Tab switch, Note open/edit, Settings open/switch, Mail navigation, Calendar drag, Files, Photos, Money and Search.
  • A client evidence matrix for #663 rules 1–9, with unverified cells marked U and narrow mechanism passes distinguished from full budget acceptance.
  • Compared dev with merge-round-7a and relevant round-7b snapshots, including Mail layouts, Settings retention, cache/snapshot/mutation primitives, queued DESIGN rules and Note linked-open work. Branch SHAs and file/line evidence are in audit-findings.md.

New self-contained issues after duplicate search

  • #747: optional syntax grammars delay usable Note editor mount.
  • #749: reminder decorations scan the Note on selection-only editor updates.
  • #751: Calendar drag repeats column geometry reads and scans all snap edges.
    Each includes context, source revision, reasoned impact, concrete fix and acceptance tests. None is labelled BLOCKER; this audit proved no merge-blocking security issue.

Existing owners received evidence instead of duplicate issues

  • #703: full Note directory retention and broad Explorer invalidation.
  • #680: Files listAll fetches all pages and derives from cumulative arrays.
  • #682: Photos retains visited tiles/aspects/index data without a resident cap.
  • #672: queued Mail virtualises DOM but active rows/thread headers still accumulate.
  • #692: retained hidden Settings job groups keep full event-triggered reads.
  • #673: Money register renders all returned rows and mutations await reload.
  • #679: Calendar cold range waits for a second Task source read.
  • #675: Search preview entry cap lacks revision/byte limits; result virtualization is already bounded.

Verification output, verbatim
Source assertions against audited Git snapshots:

audit source checks: 12 passed
changed-file check: audit-findings.md only
new issue links: 3 passed

Production resolver operation count (fresh final run; exit 0):

item edges=100; edge visits per snap=100
item edges=1000; edge visits per snap=1000
item edges=10000; edge visits per snap=10000

Required origin/dev integration:

Already up to date.

git diff --check and git diff --check origin/dev HEAD: no output, exit 0. Final worktree status: no output, clean.
Cleanup with the required Cargo settings and preset target directory:

     Removed 1 file, 356B total
web build cleanup: no build output remains

Rust fmt/clippy/test and bun check/test: NOT RUN. No crate/package changed; this read-mostly job explicitly asks to keep builds minimal. No API behavior changed, so no adversarial server probe was run. No existing test expectations changed.

Known gaps

  • Operation counts are not browser latency, CPU or RSS measurements. No production build, perf VM timing or controlled baseline comparison was needed to prove these dependencies/caps. No budget success or regression ratio is claimed.
  • Queued branches are separate source snapshots, not one tested build. Full production/HDD acceptance remains with #641/#549 and the existing mode owners.
  • Stacked backdrop filters and static Settings imports are trace candidates, not proved GPU/latency findings. Calendar overlap layout already stays separate from drag previews; a whole-grid layout cascade is not claimed.
  • Server-side rules are outside this client audit.

Decisions

  • Use #663 and queued instant-663's Instant interactions rules as the bar: dev has no §58, while round 7a's §58 is agent discovery. The orchestrator must reconcile that section-number collision.
  • Keep source evidence separate from timings; do not import another branch's measurements as proof of this code.
  • Add evidence to existing adoption owners and file only three focused runtime slices. No new dependency or product primitive.

UX gaps closed
None: read-mostly audit. It identifies fixes and acceptance tests.

UX gaps left
Follow-up fixes must preserve keyboard/touch/screen-reader behavior, stable links, selection across evicted pages, Undo, offline/error states, content safety and cross-view consistency. The new issues include the relevant tests.

Finished perf-arch-client: browser runtime audit. Branch: job/perf-arch-client Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev) Head: 93f0db72131894d3f1b32e0cf0b1751a56950e96 Commits: 0eed90202 (scope), efe200432 (ranked findings), 93f0db721 (verification). Files: audit-findings.md only. Ignored evidence/probe files remain under artifacts/perf-arch-client/. No product edits, pushes or deploys. The required fetch and origin/dev integration returned “Already up to date.” Built - Ranked runtime findings for Tab switch, Note open/edit, Settings open/switch, Mail navigation, Calendar drag, Files, Photos, Money and Search. - A client evidence matrix for #663 rules 1–9, with unverified cells marked U and narrow mechanism passes distinguished from full budget acceptance. - Compared dev with merge-round-7a and relevant round-7b snapshots, including Mail layouts, Settings retention, cache/snapshot/mutation primitives, queued DESIGN rules and Note linked-open work. Branch SHAs and file/line evidence are in audit-findings.md. New self-contained issues after duplicate search - #747: optional syntax grammars delay usable Note editor mount. - #749: reminder decorations scan the Note on selection-only editor updates. - #751: Calendar drag repeats column geometry reads and scans all snap edges. Each includes context, source revision, reasoned impact, concrete fix and acceptance tests. None is labelled BLOCKER; this audit proved no merge-blocking security issue. Existing owners received evidence instead of duplicate issues - #703: full Note directory retention and broad Explorer invalidation. - #680: Files listAll fetches all pages and derives from cumulative arrays. - #682: Photos retains visited tiles/aspects/index data without a resident cap. - #672: queued Mail virtualises DOM but active rows/thread headers still accumulate. - #692: retained hidden Settings job groups keep full event-triggered reads. - #673: Money register renders all returned rows and mutations await reload. - #679: Calendar cold range waits for a second Task source read. - #675: Search preview entry cap lacks revision/byte limits; result virtualization is already bounded. Verification output, verbatim Source assertions against audited Git snapshots: ``` audit source checks: 12 passed changed-file check: audit-findings.md only new issue links: 3 passed ``` Production resolver operation count (fresh final run; exit 0): ``` item edges=100; edge visits per snap=100 item edges=1000; edge visits per snap=1000 item edges=10000; edge visits per snap=10000 ``` Required origin/dev integration: ``` Already up to date. ``` git diff --check and git diff --check origin/dev HEAD: no output, exit 0. Final worktree status: no output, clean. Cleanup with the required Cargo settings and preset target directory: ``` Removed 1 file, 356B total web build cleanup: no build output remains ``` Rust fmt/clippy/test and bun check/test: NOT RUN. No crate/package changed; this read-mostly job explicitly asks to keep builds minimal. No API behavior changed, so no adversarial server probe was run. No existing test expectations changed. Known gaps - Operation counts are not browser latency, CPU or RSS measurements. No production build, perf VM timing or controlled baseline comparison was needed to prove these dependencies/caps. No budget success or regression ratio is claimed. - Queued branches are separate source snapshots, not one tested build. Full production/HDD acceptance remains with #641/#549 and the existing mode owners. - Stacked backdrop filters and static Settings imports are trace candidates, not proved GPU/latency findings. Calendar overlap layout already stays separate from drag previews; a whole-grid layout cascade is not claimed. - Server-side rules are outside this client audit. Decisions - Use #663 and queued instant-663's Instant interactions rules as the bar: dev has no §58, while round 7a's §58 is agent discovery. The orchestrator must reconcile that section-number collision. - Keep source evidence separate from timings; do not import another branch's measurements as proof of this code. - Add evidence to existing adoption owners and file only three focused runtime slices. No new dependency or product primitive. UX gaps closed None: read-mostly audit. It identifies fixes and acceptance tests. UX gaps left Follow-up fixes must preserve keyboard/touch/screen-reader behavior, stable links, selection across evicted pages, Undo, offline/error states, content safety and cross-view consistency. The new issues include the relevant tests.
Author
Owner

Finished perf-arch-io (#663).

Built: a read-only disk IO/startup architecture audit with a startup phase trace, background IO inventory, six source-backed findings, concrete fixes and proposed regression tests. Product code was not changed. The full committed audit text follows below so it is reviewable without a branch push.

Files: audit-findings.md only. Branch: job/perf-arch-io. Base origin/dev: c4a61e8cf0. HEAD: 935e0a2841.
Atomic commits: a7f130607 (first findings), ef1826b8b (worker trace and issue links), 935e0a284 (verified final report).

New focused issues: #748 unchanged-restart snapshots, #750 whole-Home shared mutation lock, #806 duplicate recursive watcher registrations. Detailed evidence and proposed tests added to existing #704 Notes, #695 Search and #683 Photos to avoid duplicate owners. Startup evidence also posted on #549 for hddsql. No issues closed.

Final fetch/merge output, verbatim:

Already up to date.

Gate wrapper output, verbatim; the underlying checks emitted no other output:

cargo fmt --check: exit 0
git diff --check: exit 0

Per-crate clippy/test and web gates: not run; no crate, route, contract or web file changed. No new API required an adversarial round. UX gaps closed/left: not applicable; no UI feature changed.
Cleanup output, verbatim:

     Removed 1 file, 356B total
No web build output: apps/web/build
No web build output: apps/web/.svelte-kit

Working tree clean. No push or deploy. The authorized merge from origin/dev was a no-op.

Known gaps: no new startup latency, CPU/RSS, disk-byte or burst measurements. Findings are reasoned from code; quoted #549 results are prior runs, not new audit numbers. The exact 300 s hddsql readiness timeout remains unassigned to a phase. OCR/voice transcription workers are absent from the audited build. Performance budgets are not claimed to pass.

Decisions: use #663 and queued job/instant-663 performance rules because DESIGN §58 collides with agent discovery in merge-round-7a; record source-proved lock/loop behavior without adding load for unnecessary timing; reuse existing mode rule-8 owner issues instead of filing duplicates; preserve required source durability. No product design was invented.


Disk IO and startup audit (#663)

Audit date: 2026-10-02. Branch: job/perf-arch-io.
Product base: origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
This report changes no product code. Impact estimates are reasoned unless
the text names a measured source. No new latency measurement is claimed.

Bar and source scope

Read CLAUDE.md, CONTEXT.md and the relevant DESIGN decisions. Use #663
rule 8: give reads priority and yield between bounded background batches.
On the base, DESIGN ends at §57. The queued job/instant-663 contains
§58 Instant interactions. In origin/job/merge-round-7a, §58 instead covers
agent discovery (#630). Apply the performance rules from job/instant-663
and #663. The section numbers need reconciliation by the merge owner.

Check queued product changes before filing a finding. Keep original and
queued source revisions separate. paused-queue.txt is outside the repo at
~/.local/state/codex-jobs/calternal/paused-queue.txt.

Findings recorded during review

IO-1: Full Index snapshot delays every restart

Filed: #748.

crates/calternal-server/src/wire.rs:1135 awaits Db::snapshot before
migrations. main.rs:371 awaits this whole setup before binding HTTP.
crates/calternal-db/src/snapshot.rs:64 uses VACUUM INTO on the single
writer connection, then syncs the complete output and its parent. There is
no pending-migration check. This also occurs in merge-round-7a at
wire.rs:1143. A restart with no schema change still reads and writes a
complete Index before readiness. It also prunes scheduled backup retention.

Impact: startup work grows with the live Index. Cached reads can remove
physical source reads, but cannot remove output writes and the final sync.
This can extend deploy downtime on a large Index. No duration is measured.

Fix: preserve the pre-upgrade snapshot requirement, but first check the
registered migration versions and checksums. Take that snapshot only when
an unapplied migration will run. Keep scheduled backups separate. Test
first start, no-change restart, upgrade, changed checksum and snapshot failure.

IO-2: Files startup scan holds the shared mutation lock for a whole Home

Filed: #750.

crates/plugins/files/src/index.rs:80 holds Root::lock_mutation across
the complete recursive folder loop. reconcile_folder_locked:184 lists
disk entries, :265 prepares each record, and :1077 hashes changed files
before the lock is released. The lock is shared across Root clones.
Merge-round-7a retains this scope (index.rs:91).

Impact: an interactive filesystem write can wait behind all remaining
directories and changed-file bytes in a Home. Deferring the scan until
after socket bind does not remove that wait. Lower OS thread priority does
not shorten a held application lock. Separate WAL readers help pure Index
reads; they do not help a mutation waiting for this lock.

Fix: scan and hash outside the mutation lock. Use bounded pages. Acquire the
lock only to revalidate file fingerprints and publish a bounded batch, then
release it before more IO. Keep move/Trash recovery and identity invariants.
Test a held slow hash during reconcile: a write in another folder and another
User's Home must complete before that hash is released. Test concurrent
rename, delete and overwrite without stale Index rows.

IO-3: Notes scans and rebuilds unchanged Markdown more than once

Added source evidence and proposed regression tests to existing
#704. No duplicate issue.

crates/plugins/notes/src/lib.rs:99 calls Task reconciliation, then Note
reconciliation. tasks_store.rs:695 and store.rs:1932 each call scan.
store.rs:1884 walks the visible Home and reads every Markdown file into a
complete text list. The first pass calls store::index at
tasks_store.rs:718; the second calls it at store.rs:1935.
store::index:671 builds both projections. index_note_projection:715
starts its writer transaction without a source-hash check.

The Files completion listener (lib.rs:2063) and the hourly job
(wire.rs:5025) use this full path. lib.rs:2100 holds the per-User lock.
Merge-round-7a retains both scans. Queued #653 changes write publication,
not these loops. Unchanged files still cost reads, parsing and Index writes.
On a host-cached disk, parsing and writer occupancy remain. Synchronous
scan IO also occupies a Tokio worker. No duration is measured here.

Fix: one bounded source scan, then dependency-ordered Task and Note batches.
Use source fingerprints and hashes to skip unchanged projections. Run file
IO off Tokio. Keep stable IDs and same-size/timestamp restore detection.
Test source-read counts, unchanged second reconcile, dependent Task/Log
ordering and edits between batches. Normal Journal snapshot reads already
use WAL (#549); do not claim they still take the full-reconcile guard.

IO-4: Search scans re-read and parse unchanged source files

Added source evidence and proposed regression tests to existing
#695. No duplicate issue.

Keyword reconciliation runs every five minutes (indexer.rs:55,1175).
scan_tree:1498 calls indexed_file before comparing the manifest at
:1511. Every eligible PDF, up to 16 MiB, is read, hashed and extracted
before this comparison (:2704). Full integrity checks first count the
tree (:2054), then walk it again. The actor has nice=10, but no byte pacing
or idle IO class. Existing 1,024-file/32 MiB write batches avoid per-file
Tantivy commits. They do not cap full-scan read bandwidth.

Merge-round-7a adds bounded pages and SQLite audit state (#496). It retains
read/parse-before-comparison (indexer.rs:1878,1892) and the PDF extraction
path in indexed_file. Do not file the fixed memory problem again.

Semantic reconciliation also runs every five minutes
(calternal-embed/src/store.rs:406). prepare_path:1124 reads, hashes and
chunks before comparing existing hashes at :1140. Queued #503 bounds the
scan and makes model loading lazy, but retains this order at :1320,1338.
wire.rs:2924 queues semantic Home changes before bind, so sync_homes
still requests the lazy model during startup for existing Users.

Impact: repeated random file opens, source reads, parsing and PDF processes
can compete with interactive reads. Separate keyword, semantic and Notes
passes repeat some source work. OS caches reduce physical reads; they do
not remove parsing, lookups or processes. No new timing is claimed.

Fix: reuse verified source revisions and parsed ingest projections. Compare
content hashes before PDF extraction or semantic chunking. Keep a bounded,
rotating integrity pass to detect equal-size/timestamp external changes.
Pace bytes between pages. Test a counting PDF extractor on unchanged second
reconcile, equal-size replacement, deleted hits and queries during backfill.

IO-5: Photos full refresh holds the Index writer for a whole library

Added source evidence and proposed regression tests to existing
#683. No duplicate issue.

crates/plugins/photos/src/index.rs:264 refreshes all viewers after Files
completion. refresh_user:345 forces a rebuild. Full refresh holds the
shared refresh lock at :778, streams the library at :825, then acquires
the single Index writer at :874. It upserts every media item, deletes and
recreates memberships and day counts, and upserts groups before commit at
:970. No row, byte or time limit bounds that transaction. Forced refresh
bypasses the unchanged return at :854. Merge-round-7a does not change this
file. The bounded incremental path does not bound this full-refresh path.

Impact: interactive mutations and queue heartbeats wait for a whole-library
publication. WAL readers can still see old committed rows. Changed-media
parsing can retain a reader connection while file work runs. Do not claim
that the SQLite writer prevents all reads. No duration is measured.

Fix: bounded staging batches and a short atomic generation publication.
Keep the complete old timeline until the new groups and counts are ready.
Release reader connections before media parsing. Skip unchanged rebuilds
while preserving lost-event and deleted-row repair. Test 50k media with an
unrelated mutation between batches and complete old/new timeline reads.

IO-6: Duplicate recursive watcher walks run before readiness

Filed: #806.

Search registers a recursive Home watcher before it returns
(indexer.rs:504,2597). Collaboration registers another in Hub::new
(calternal-collab/src/session.rs:703). Both run before HTTP bind. The Home
watcher starts a third registration inside a spawned Tokio task
(wire.rs:5538,5554). Merge-round-7a retains these registrations.

Verified against the installed notify 8.2.0 source, matching Cargo.lock:
src/inotify.rs:400 uses WalkDir for recursive registration; :547 waits
on rx.recv until registration returns. Thus each registration walks the
directory tree on Linux. This is separate from Search content reconciliation,
which is deferred. Large nested Homes increase pre-listener metadata work.
The Home watcher can also occupy a Tokio worker during registration.

Fix: reuse one server-owned Home watcher and its overflow-to-reconcile
signal. Alternatively, register off Tokio after bind with a barrier that
covers changes during registration. Test registration count, delayed
registration, writes during setup and overflow recovery. Measure this phase
in #549 before attributing its readiness timeout to watcher setup.

Startup to readiness

Source lines below refer to the product base, not the audit branch HEAD.

Phase Work and IO Readiness and priority
Root setup (wire.rs:1087-1102) Open split roots, prepare user data, probe writes, replay pending filesystem journal Required before bind; recovery changes must be safe before serving
Index setup (:1129-1220) Open WAL pools, full snapshot, validate/apply namespaced migrations Before bind; IO-1. Applied migrations compare checksums and do not rerun SQL
Auth/config (:1222-1266) Plugin state, secrets, notification setup, OIDC discovery and pending deletions Before bind; OIDC is a serial external-call dependency, not disk IO
Search open (:1268) Open Tantivy reader/writer, load manifest, recursively register watcher Before bind; IO-6. Deferred content scan keeps persisted Search available
Semantic open (:1276) Open legacy vector SQLite and schema; spawn model/index workers DB/schema before bind; model load and scan can overlap later startup work
Home/recovery (:1380-1395) Sync default roots/quotas, queue Home changes, restore upload reservations, replay Note/Tag intents Before bind; inspect these phases for large User or pending-work sets
Collaboration (:1408) Create Hub and register its recursive watcher Before bind; IO-6
Background/listener (:1508, main.rs:372) Start durable workers, bind HTTP, release startup barrier Durable jobs may start before bind. Startup full reconcile waits for barrier
Reconcile (wire.rs:1531-1550) Keyword Search, private generation jobs, Files, fallback Notes on Files failure, Tags Low-priority current-thread runtime. Files completion wakes Notes and Photos separately
Readiness (:4578) SELECT 1 on reader pool, then filesystem write probes Does not wait for all projections or thumbnails; ready is not background-complete

The startup thread uses nice=10 and the idle IO class (wire.rs:5225).
That does not set the priority of already-open SQLx workers, separate Search
actors, shared blocking-pool tasks or media children. It also does not release
the locks in IO-2 or IO-5. A socket can be ready while these queues still
compete with interactive work.

Background IO inventory

Worker IO pattern and bound Assessment
Keyword indexer Directory metadata plus random text/PDF opens; bounded Tantivy/SQLite commits; five-minute full checks IO-4; nice=10 alone does not pace IO
Private Search rebuilds One queued User rebuild at a time per kind; staged Tantivy generations and bounded file batches Preserve atomic generation publication; use the same IO budget as reconcile
Files adoption Directory walks and whole changed-file BLAKE3 reads; unchanged hash reuse; 64-row SQL statements IO-2; SQL statement size does not bound the whole-Home lock
Notes/Tasks Two full Markdown scans, per-file projection transactions; hourly plus completion/lag recovery IO-3; repeat writes remain with cached sources
Tags Full repair after startup Files pass; normal changes use indexed paths Shares the Index writer; no separate interactive-aware IO budget established
Photos metadata Sequential Index enumeration plus changed-media EXIF/Sidecar reads; whole-library writer publication on full refresh IO-5; incremental refresh is bounded
Semantic embeddings Private User WAL files; random text reads and hash lookups; inference groups of 16 chunks IO-4; #503 bounds memory, not a global background byte budget
Photos CLIP Durable keyset batches of eight; reads cached 256px thumbnails; queues missing thumbnails and retries Good bounded unit; no whole-source image reads for every CLIP query
Thumbnails Two sizes (256/1024); shared media semaphore of two; capped input/output and wall times Each output syncs file and directory. IO is bounded per item, not paced by interactive load
Video conversion Durable dedup by hash/profile; max two jobs; same media semaphore as thumbnails; resumable HLS output Sequential media work can compete with requests. Do not assume a thread-priority setting reaches child processes
Mail sync Three account workers; 80-UID windows; max 100 backfill windows/run; atomic window+cursor transactions WAL reads are separate; provider waits happen outside the window transaction. No per-message filesystem fsync in this path
Index backups VACUUM INTO, output sync, retention prune Required durability; writer occupancy also occurs in scheduled backup jobs
Blob store scrub 64-entry pages, cursor persistence, 1 MiB/s pacing, own nice/idle thread Existing model for low-priority bounded maintenance (§5)
OCR and voice transcription No built worker or startup hook in audited server/workspace Research/queued work, not a startup cost in this revision; do not infer costs from unmerged experiments

Core Index connections use WAL and synchronous=NORMAL (db.rs:52-54).
Do not describe every Index transaction as a file fsync. Checkpoints and
full output syncs still write to disk. Source-file and thumbnail publication
retain their required atomic-write syncs. This audit does not propose to
weaken source durability to improve a benchmark.

Readiness probes are not read-only disk operations. Root::probe_system_writable
(root.rs:776) creates, syncs and removes a temporary in each of the system
root and .system, then syncs each directory (:792). Include health polling
in idle IO measurements. These probes check a real required capability; this
audit does not call them a defect or propose to remove the writable check.

Relationship to #549 hddsql

Read #549 and its comments, the dated Tab report, and the two added tracing
commits in origin/job/hddsql-549 at
b7c9e36f98722578b19b59cc075b050c6f89ecdb.

The hddsql report on #549 qualifies its emulator at QD1 125.008 IOPS,
p50 7.963 ms and p99 8.225 ms. Its full seeded restart misses the 300 s
readiness deadline. It reports VACUUM INTO at 737 ms, semantic LSH inserts
at 87–131 ms and one semantic document insert at 51 ms. These statements
do not explain the 300 s wait. Repeated Tantivy commits and pool waits are
reported before readiness. This audit supplies code paths to instrument;
it does not claim to have identified that timeout's exact cause.

The earlier #549 Journal stall is distinct. Normal Journal GET now reads
complete committed source snapshots through WAL. The dated corrected probe
reports 41.282 ms end to end and 0.837 ms in plugin phases. These are existing
measurements on another build. They are not results for this audit revision.

On a host-cached HDD-like disk, cold physical reads and warm logical work
must be recorded separately. Process-wide read bytes include background
work and cannot be assigned to one route without isolation. Warm zero read
bytes do not prove zero parsing, writer wait or fsync cost.

docs/perf/baseline.json has no matching phase-by-phase startup plus
background-contention baseline for this audited revision. Do not compute a
regression ratio from unrelated API or Tab fixtures. Reuse #549's startup
phase instrumentation and report readiness separately from projection
completion, alongside foreground reads/writes during recovery.

Queued branch review

Merge-round-7a source: 2f4482ded066d9c5d9c59130377907f7fd2916c9.
It includes bounded Search/semantic scans (#496/#503), which this report
credits. It does not remove IO-1 through IO-6.

Checked the round-7b list in the external queue and inspected its crate-file
diffs against the audit base. Branch revisions are recorded for the changed
IO implementations:

  • job/ryw-653 at 4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63 changes
    Journal mutation publication, not the full-reconcile loops.
  • job/voicefiles-620 at b7ef7a2ab57f45b5d46cd19b4560215acae918e3 adds
    bounded media-header reads to Files/Search MIME detection. It retains
    IO-2 and IO-4. These new reads can increase warm scan work; no regression
    size is measured here.
  • job/calimg-589 at 421dd63735d116cba4961a0a3ca4c985baa83480 changes
    text-card excerpts, not thumbnail scheduling or sync policy.
  • job/perf-mut-667 at 52d2b17f805072cd0304d7a05fe0534523cc7bc3 changes
    Mail preference revisions, not sync window publication.

The queue's blaze-settings, instant-663, writeonopen-661, admin-burst-705,
perf-cache-665, fix-499, perf-snap-666, imaptest-625 and burst-709 branches
have no changed implementation in the audited IO files. UI/transport changes
are not evidence that server startup IO is fixed.

Issue ownership and decisions

Searched all open and closed issue titles. Read #23, #549 and its comments,
#476, #470, #496, #503 and the relevant rule-8 issues. File focused new work
only for unchanged restart snapshots (#748), whole-Home mutation lock (#750)
and duplicate watcher registration (#806). Put overlapping Notes, Search
and Photos evidence in #704, #695 and #683. No security blocker was proved.

Decisions: use reasoned source evidence where the loop/lock scope proves the
finding; do not load the shared host or perf VM to invent timing numbers.
Use #663 and the queued instant-interactions section despite the §58 number
collision. Keep all product fixes with their issue owners. No product/API/UI
changes, new dependency, feature benchmark or source durability change.

Verification and limits

No crate or package version changed. No build or perf VM session has run.
No API changes require a real-server adversarial round. No UI changes require
screenshots or UX acceptance checks. Rust/web tests are not run for this
Markdown-only audit. Final git fetch origin and git merge origin/dev ran
once. Merge output, verbatim:

Already up to date.

The fetched product base remains c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
No crate was changed, so per-crate clippy/test gates and web gates do not
apply. Formatting and diff check output, verbatim (the wrappers print the
command exit status; both commands produced no other output):

cargo fmt --check: exit 0
git diff --check: exit 0

Only audit-findings.md is tracked by this job. Its text was re-read against
the audited sources. Issue-body scratch files stay in ignored artifacts/.

Known gaps: no measured startup phase durations, background p50/p95 latency,
CPU/RSS, physical bytes or request burst for this source revision. The exact
#549 readiness timeout remains with hddsql. No OCR/voice implementation was
available to measure. Future fixes must include the bounded cold/warm
contention tests specified above. This is an architecture audit, not a claim
that any interaction meets the performance budgets.

Finished perf-arch-io (#663). Built: a read-only disk IO/startup architecture audit with a startup phase trace, background IO inventory, six source-backed findings, concrete fixes and proposed regression tests. Product code was not changed. The full committed audit text follows below so it is reviewable without a branch push. Files: audit-findings.md only. Branch: job/perf-arch-io. Base origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. HEAD: 935e0a2841fa2143977d577d8af55ee40a075995. Atomic commits: a7f130607 (first findings), ef1826b8b (worker trace and issue links), 935e0a284 (verified final report). New focused issues: #748 unchanged-restart snapshots, #750 whole-Home shared mutation lock, #806 duplicate recursive watcher registrations. Detailed evidence and proposed tests added to existing #704 Notes, #695 Search and #683 Photos to avoid duplicate owners. Startup evidence also posted on #549 for hddsql. No issues closed. Final fetch/merge output, verbatim: ```text Already up to date. ``` Gate wrapper output, verbatim; the underlying checks emitted no other output: ```text cargo fmt --check: exit 0 git diff --check: exit 0 ``` Per-crate clippy/test and web gates: not run; no crate, route, contract or web file changed. No new API required an adversarial round. UX gaps closed/left: not applicable; no UI feature changed. Cleanup output, verbatim: ```text Removed 1 file, 356B total No web build output: apps/web/build No web build output: apps/web/.svelte-kit ``` Working tree clean. No push or deploy. The authorized merge from origin/dev was a no-op. Known gaps: no new startup latency, CPU/RSS, disk-byte or burst measurements. Findings are reasoned from code; quoted #549 results are prior runs, not new audit numbers. The exact 300 s hddsql readiness timeout remains unassigned to a phase. OCR/voice transcription workers are absent from the audited build. Performance budgets are not claimed to pass. Decisions: use #663 and queued job/instant-663 performance rules because DESIGN §58 collides with agent discovery in merge-round-7a; record source-proved lock/loop behavior without adding load for unnecessary timing; reuse existing mode rule-8 owner issues instead of filing duplicates; preserve required source durability. No product design was invented. --- # Disk IO and startup audit (#663) Audit date: 2026-10-02. Branch: `job/perf-arch-io`. Product base: `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. This report changes no product code. Impact estimates are reasoned unless the text names a measured source. No new latency measurement is claimed. ## Bar and source scope Read `CLAUDE.md`, `CONTEXT.md` and the relevant DESIGN decisions. Use #663 rule 8: give reads priority and yield between bounded background batches. On the base, DESIGN ends at §57. The queued `job/instant-663` contains §58 Instant interactions. In `origin/job/merge-round-7a`, §58 instead covers agent discovery (#630). Apply the performance rules from `job/instant-663` and #663. The section numbers need reconciliation by the merge owner. Check queued product changes before filing a finding. Keep original and queued source revisions separate. `paused-queue.txt` is outside the repo at `~/.local/state/codex-jobs/calternal/paused-queue.txt`. ## Findings recorded during review ### IO-1: Full Index snapshot delays every restart Filed: [#748](https://git.kayg.org/kayg/calternal/issues/748). `crates/calternal-server/src/wire.rs:1135` awaits `Db::snapshot` before migrations. `main.rs:371` awaits this whole setup before binding HTTP. `crates/calternal-db/src/snapshot.rs:64` uses `VACUUM INTO` on the single writer connection, then syncs the complete output and its parent. There is no pending-migration check. This also occurs in merge-round-7a at `wire.rs:1143`. A restart with no schema change still reads and writes a complete Index before readiness. It also prunes scheduled backup retention. Impact: startup work grows with the live Index. Cached reads can remove physical source reads, but cannot remove output writes and the final sync. This can extend deploy downtime on a large Index. No duration is measured. Fix: preserve the pre-upgrade snapshot requirement, but first check the registered migration versions and checksums. Take that snapshot only when an unapplied migration will run. Keep scheduled backups separate. Test first start, no-change restart, upgrade, changed checksum and snapshot failure. ### IO-2: Files startup scan holds the shared mutation lock for a whole Home Filed: [#750](https://git.kayg.org/kayg/calternal/issues/750). `crates/plugins/files/src/index.rs:80` holds `Root::lock_mutation` across the complete recursive folder loop. `reconcile_folder_locked:184` lists disk entries, `:265` prepares each record, and `:1077` hashes changed files before the lock is released. The lock is shared across Root clones. Merge-round-7a retains this scope (`index.rs:91`). Impact: an interactive filesystem write can wait behind all remaining directories and changed-file bytes in a Home. Deferring the scan until after socket bind does not remove that wait. Lower OS thread priority does not shorten a held application lock. Separate WAL readers help pure Index reads; they do not help a mutation waiting for this lock. Fix: scan and hash outside the mutation lock. Use bounded pages. Acquire the lock only to revalidate file fingerprints and publish a bounded batch, then release it before more IO. Keep move/Trash recovery and identity invariants. Test a held slow hash during reconcile: a write in another folder and another User's Home must complete before that hash is released. Test concurrent rename, delete and overwrite without stale Index rows. ### IO-3: Notes scans and rebuilds unchanged Markdown more than once Added source evidence and proposed regression tests to existing [#704](https://git.kayg.org/kayg/calternal/issues/704). No duplicate issue. `crates/plugins/notes/src/lib.rs:99` calls Task reconciliation, then Note reconciliation. `tasks_store.rs:695` and `store.rs:1932` each call `scan`. `store.rs:1884` walks the visible Home and reads every Markdown file into a complete text list. The first pass calls `store::index` at `tasks_store.rs:718`; the second calls it at `store.rs:1935`. `store::index:671` builds both projections. `index_note_projection:715` starts its writer transaction without a source-hash check. The Files completion listener (`lib.rs:2063`) and the hourly job (`wire.rs:5025`) use this full path. `lib.rs:2100` holds the per-User lock. Merge-round-7a retains both scans. Queued #653 changes write publication, not these loops. Unchanged files still cost reads, parsing and Index writes. On a host-cached disk, parsing and writer occupancy remain. Synchronous scan IO also occupies a Tokio worker. No duration is measured here. Fix: one bounded source scan, then dependency-ordered Task and Note batches. Use source fingerprints and hashes to skip unchanged projections. Run file IO off Tokio. Keep stable IDs and same-size/timestamp restore detection. Test source-read counts, unchanged second reconcile, dependent Task/Log ordering and edits between batches. Normal Journal snapshot reads already use WAL (#549); do not claim they still take the full-reconcile guard. ### IO-4: Search scans re-read and parse unchanged source files Added source evidence and proposed regression tests to existing [#695](https://git.kayg.org/kayg/calternal/issues/695). No duplicate issue. Keyword reconciliation runs every five minutes (`indexer.rs:55,1175`). `scan_tree:1498` calls `indexed_file` before comparing the manifest at `:1511`. Every eligible PDF, up to 16 MiB, is read, hashed and extracted before this comparison (`:2704`). Full integrity checks first count the tree (`:2054`), then walk it again. The actor has nice=10, but no byte pacing or idle IO class. Existing 1,024-file/32 MiB write batches avoid per-file Tantivy commits. They do not cap full-scan read bandwidth. Merge-round-7a adds bounded pages and SQLite audit state (#496). It retains read/parse-before-comparison (`indexer.rs:1878,1892`) and the PDF extraction path in `indexed_file`. Do not file the fixed memory problem again. Semantic reconciliation also runs every five minutes (`calternal-embed/src/store.rs:406`). `prepare_path:1124` reads, hashes and chunks before comparing existing hashes at `:1140`. Queued #503 bounds the scan and makes model loading lazy, but retains this order at `:1320,1338`. `wire.rs:2924` queues semantic Home changes before bind, so `sync_homes` still requests the lazy model during startup for existing Users. Impact: repeated random file opens, source reads, parsing and PDF processes can compete with interactive reads. Separate keyword, semantic and Notes passes repeat some source work. OS caches reduce physical reads; they do not remove parsing, lookups or processes. No new timing is claimed. Fix: reuse verified source revisions and parsed ingest projections. Compare content hashes before PDF extraction or semantic chunking. Keep a bounded, rotating integrity pass to detect equal-size/timestamp external changes. Pace bytes between pages. Test a counting PDF extractor on unchanged second reconcile, equal-size replacement, deleted hits and queries during backfill. ### IO-5: Photos full refresh holds the Index writer for a whole library Added source evidence and proposed regression tests to existing [#683](https://git.kayg.org/kayg/calternal/issues/683). No duplicate issue. `crates/plugins/photos/src/index.rs:264` refreshes all viewers after Files completion. `refresh_user:345` forces a rebuild. Full refresh holds the shared refresh lock at `:778`, streams the library at `:825`, then acquires the single Index writer at `:874`. It upserts every media item, deletes and recreates memberships and day counts, and upserts groups before commit at `:970`. No row, byte or time limit bounds that transaction. Forced refresh bypasses the unchanged return at `:854`. Merge-round-7a does not change this file. The bounded incremental path does not bound this full-refresh path. Impact: interactive mutations and queue heartbeats wait for a whole-library publication. WAL readers can still see old committed rows. Changed-media parsing can retain a reader connection while file work runs. Do not claim that the SQLite writer prevents all reads. No duration is measured. Fix: bounded staging batches and a short atomic generation publication. Keep the complete old timeline until the new groups and counts are ready. Release reader connections before media parsing. Skip unchanged rebuilds while preserving lost-event and deleted-row repair. Test 50k media with an unrelated mutation between batches and complete old/new timeline reads. ### IO-6: Duplicate recursive watcher walks run before readiness Filed: [#806](https://git.kayg.org/kayg/calternal/issues/806). Search registers a recursive Home watcher before it returns (`indexer.rs:504,2597`). Collaboration registers another in `Hub::new` (`calternal-collab/src/session.rs:703`). Both run before HTTP bind. The Home watcher starts a third registration inside a spawned Tokio task (`wire.rs:5538,5554`). Merge-round-7a retains these registrations. Verified against the installed notify 8.2.0 source, matching Cargo.lock: `src/inotify.rs:400` uses WalkDir for recursive registration; `:547` waits on `rx.recv` until registration returns. Thus each registration walks the directory tree on Linux. This is separate from Search content reconciliation, which is deferred. Large nested Homes increase pre-listener metadata work. The Home watcher can also occupy a Tokio worker during registration. Fix: reuse one server-owned Home watcher and its overflow-to-reconcile signal. Alternatively, register off Tokio after bind with a barrier that covers changes during registration. Test registration count, delayed registration, writes during setup and overflow recovery. Measure this phase in #549 before attributing its readiness timeout to watcher setup. ## Startup to readiness Source lines below refer to the product base, not the audit branch HEAD. | Phase | Work and IO | Readiness and priority | | --- | --- | --- | | Root setup (`wire.rs:1087-1102`) | Open split roots, prepare user data, probe writes, replay pending filesystem journal | Required before bind; recovery changes must be safe before serving | | Index setup (`:1129-1220`) | Open WAL pools, full snapshot, validate/apply namespaced migrations | Before bind; IO-1. Applied migrations compare checksums and do not rerun SQL | | Auth/config (`:1222-1266`) | Plugin state, secrets, notification setup, OIDC discovery and pending deletions | Before bind; OIDC is a serial external-call dependency, not disk IO | | Search open (`:1268`) | Open Tantivy reader/writer, load manifest, recursively register watcher | Before bind; IO-6. Deferred content scan keeps persisted Search available | | Semantic open (`:1276`) | Open legacy vector SQLite and schema; spawn model/index workers | DB/schema before bind; model load and scan can overlap later startup work | | Home/recovery (`:1380-1395`) | Sync default roots/quotas, queue Home changes, restore upload reservations, replay Note/Tag intents | Before bind; inspect these phases for large User or pending-work sets | | Collaboration (`:1408`) | Create Hub and register its recursive watcher | Before bind; IO-6 | | Background/listener (`:1508`, `main.rs:372`) | Start durable workers, bind HTTP, release startup barrier | Durable jobs may start before bind. Startup full reconcile waits for barrier | | Reconcile (`wire.rs:1531-1550`) | Keyword Search, private generation jobs, Files, fallback Notes on Files failure, Tags | Low-priority current-thread runtime. Files completion wakes Notes and Photos separately | | Readiness (`:4578`) | `SELECT 1` on reader pool, then filesystem write probes | Does not wait for all projections or thumbnails; ready is not background-complete | The startup thread uses nice=10 and the idle IO class (`wire.rs:5225`). That does not set the priority of already-open SQLx workers, separate Search actors, shared blocking-pool tasks or media children. It also does not release the locks in IO-2 or IO-5. A socket can be ready while these queues still compete with interactive work. ## Background IO inventory | Worker | IO pattern and bound | Assessment | | --- | --- | --- | | Keyword indexer | Directory metadata plus random text/PDF opens; bounded Tantivy/SQLite commits; five-minute full checks | IO-4; nice=10 alone does not pace IO | | Private Search rebuilds | One queued User rebuild at a time per kind; staged Tantivy generations and bounded file batches | Preserve atomic generation publication; use the same IO budget as reconcile | | Files adoption | Directory walks and whole changed-file BLAKE3 reads; unchanged hash reuse; 64-row SQL statements | IO-2; SQL statement size does not bound the whole-Home lock | | Notes/Tasks | Two full Markdown scans, per-file projection transactions; hourly plus completion/lag recovery | IO-3; repeat writes remain with cached sources | | Tags | Full repair after startup Files pass; normal changes use indexed paths | Shares the Index writer; no separate interactive-aware IO budget established | | Photos metadata | Sequential Index enumeration plus changed-media EXIF/Sidecar reads; whole-library writer publication on full refresh | IO-5; incremental refresh is bounded | | Semantic embeddings | Private User WAL files; random text reads and hash lookups; inference groups of 16 chunks | IO-4; #503 bounds memory, not a global background byte budget | | Photos CLIP | Durable keyset batches of eight; reads cached 256px thumbnails; queues missing thumbnails and retries | Good bounded unit; no whole-source image reads for every CLIP query | | Thumbnails | Two sizes (256/1024); shared media semaphore of two; capped input/output and wall times | Each output syncs file and directory. IO is bounded per item, not paced by interactive load | | Video conversion | Durable dedup by hash/profile; max two jobs; same media semaphore as thumbnails; resumable HLS output | Sequential media work can compete with requests. Do not assume a thread-priority setting reaches child processes | | Mail sync | Three account workers; 80-UID windows; max 100 backfill windows/run; atomic window+cursor transactions | WAL reads are separate; provider waits happen outside the window transaction. No per-message filesystem fsync in this path | | Index backups | `VACUUM INTO`, output sync, retention prune | Required durability; writer occupancy also occurs in scheduled backup jobs | | Blob store scrub | 64-entry pages, cursor persistence, 1 MiB/s pacing, own nice/idle thread | Existing model for low-priority bounded maintenance (§5) | | OCR and voice transcription | No built worker or startup hook in audited server/workspace | Research/queued work, not a startup cost in this revision; do not infer costs from unmerged experiments | Core Index connections use WAL and `synchronous=NORMAL` (`db.rs:52-54`). Do not describe every Index transaction as a file fsync. Checkpoints and full output syncs still write to disk. Source-file and thumbnail publication retain their required atomic-write syncs. This audit does not propose to weaken source durability to improve a benchmark. Readiness probes are not read-only disk operations. `Root::probe_system_writable` (`root.rs:776`) creates, syncs and removes a temporary in each of the system root and `.system`, then syncs each directory (`:792`). Include health polling in idle IO measurements. These probes check a real required capability; this audit does not call them a defect or propose to remove the writable check. ## Relationship to #549 hddsql Read #549 and its comments, the dated Tab report, and the two added tracing commits in `origin/job/hddsql-549` at `b7c9e36f98722578b19b59cc075b050c6f89ecdb`. The hddsql report on #549 qualifies its emulator at QD1 125.008 IOPS, p50 7.963 ms and p99 8.225 ms. Its full seeded restart misses the 300 s readiness deadline. It reports `VACUUM INTO` at 737 ms, semantic LSH inserts at 87–131 ms and one semantic document insert at 51 ms. These statements do not explain the 300 s wait. Repeated Tantivy commits and pool waits are reported before readiness. This audit supplies code paths to instrument; it does not claim to have identified that timeout's exact cause. The earlier #549 Journal stall is distinct. Normal Journal GET now reads complete committed source snapshots through WAL. The dated corrected probe reports 41.282 ms end to end and 0.837 ms in plugin phases. These are existing measurements on another build. They are not results for this audit revision. On a host-cached HDD-like disk, cold physical reads and warm logical work must be recorded separately. Process-wide read bytes include background work and cannot be assigned to one route without isolation. Warm zero read bytes do not prove zero parsing, writer wait or fsync cost. `docs/perf/baseline.json` has no matching phase-by-phase startup plus background-contention baseline for this audited revision. Do not compute a regression ratio from unrelated API or Tab fixtures. Reuse #549's startup phase instrumentation and report readiness separately from projection completion, alongside foreground reads/writes during recovery. ## Queued branch review Merge-round-7a source: `2f4482ded066d9c5d9c59130377907f7fd2916c9`. It includes bounded Search/semantic scans (#496/#503), which this report credits. It does not remove IO-1 through IO-6. Checked the round-7b list in the external queue and inspected its crate-file diffs against the audit base. Branch revisions are recorded for the changed IO implementations: - `job/ryw-653` at `4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63` changes Journal mutation publication, not the full-reconcile loops. - `job/voicefiles-620` at `b7ef7a2ab57f45b5d46cd19b4560215acae918e3` adds bounded media-header reads to Files/Search MIME detection. It retains IO-2 and IO-4. These new reads can increase warm scan work; no regression size is measured here. - `job/calimg-589` at `421dd63735d116cba4961a0a3ca4c985baa83480` changes text-card excerpts, not thumbnail scheduling or sync policy. - `job/perf-mut-667` at `52d2b17f805072cd0304d7a05fe0534523cc7bc3` changes Mail preference revisions, not sync window publication. The queue's blaze-settings, instant-663, writeonopen-661, admin-burst-705, perf-cache-665, fix-499, perf-snap-666, imaptest-625 and burst-709 branches have no changed implementation in the audited IO files. UI/transport changes are not evidence that server startup IO is fixed. ## Issue ownership and decisions Searched all open and closed issue titles. Read #23, #549 and its comments, #476, #470, #496, #503 and the relevant rule-8 issues. File focused new work only for unchanged restart snapshots (#748), whole-Home mutation lock (#750) and duplicate watcher registration (#806). Put overlapping Notes, Search and Photos evidence in #704, #695 and #683. No security blocker was proved. Decisions: use reasoned source evidence where the loop/lock scope proves the finding; do not load the shared host or perf VM to invent timing numbers. Use #663 and the queued instant-interactions section despite the §58 number collision. Keep all product fixes with their issue owners. No product/API/UI changes, new dependency, feature benchmark or source durability change. ## Verification and limits No crate or package version changed. No build or perf VM session has run. No API changes require a real-server adversarial round. No UI changes require screenshots or UX acceptance checks. Rust/web tests are not run for this Markdown-only audit. Final `git fetch origin` and `git merge origin/dev` ran once. Merge output, verbatim: ```text Already up to date. ``` The fetched product base remains `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. No crate was changed, so per-crate clippy/test gates and web gates do not apply. Formatting and diff check output, verbatim (the wrappers print the command exit status; both commands produced no other output): ```text cargo fmt --check: exit 0 git diff --check: exit 0 ``` Only `audit-findings.md` is tracked by this job. Its text was re-read against the audited sources. Issue-body scratch files stay in ignored `artifacts/`. Known gaps: no measured startup phase durations, background p50/p95 latency, CPU/RSS, physical bytes or request burst for this source revision. The exact #549 readiness timeout remains with hddsql. No OCR/voice implementation was available to measure. Future fixes must include the bounded cold/warm contention tests specified above. This is an architecture audit, not a claim that any interaction meets the performance budgets.
Author
Owner

Completed perf-arch-sync audit.

Built: source-evidence matrix for Mail IMAP, CalDAV client/server, CardDAV status, WebDAV, Notes collaboration, Files SSE, #668 and notifications. File: audit-findings.md (only repository change). Branch: job/perf-arch-sync. Head: 788ad816af5c0de8ccf007f8fd7321ba9e72286b. Three atomic documentation commits. Working tree clean. Base c4a61e8cf; checked round-7a 2f4482ded, queued #653 4723c5f3b, #668 7695d237f and intended performance §58 e62249ded.

Filed findings, each with context, source locations, reasoned impact, fix and tests:

  • #753: Mail IDLE uses the three ingest slots and leaves 300 s listener gaps. Concurrent #763 overlaps the slots; cross-linked for one fix owner.
  • #757: Mail delta drains all new UID windows; expunge loads the complete live set and prunes in one writer transaction.
  • #769: one external Note hint reads every loaded room for that User before matching the changed path.
  • #778: one flush timer task per dirty update; narrowed to room scheduling because concurrent #761 owns watcher queue bounds.
  • #800: WebDAV file metadata and opens wait on the global filesystem mutation lock, including unrelated Users' work.

Added evidence to existing owners #573 (DAV bounds), #677 (Calendar read awaits provider refresh), #679 (sync fairness and writer parsing), #668 (adoption, fan-out, idle tick cost and reconnect jitter), and #761 (watcher coordination).

Scratch Index evidence: actual Reminder migration/query, 10,000 retained changes for one Task. Indexed seek plan: SEARCH reminder_changes USING INDEX sqlite_autoindex_reminder_changes_1 (user_id=? AND seq>?). Fetch returns 10,000 rows before one UID remains. This proves work count, not latency. No baseline ratio claimed.

Verification: requested fetch/merge once before gates; merge output verbatim:

Already up to date.

Cargo fmt and diff check produced no stdout/stderr. Recorded exit statuses, verbatim:

cargo fmt --check exit status: 0
git diff --check exit status: 0

No changed crate or web code: clippy, crate tests, web gates and adversarial traffic not run under this read-mostly job's minimal-build instruction. Cargo env used line-tables-only, incremental=0, build jobs=4 and worktree target/tmp; preset target unchanged.

Cleanup output, verbatim:

     Removed 1 file, 356B total
cargo clean exit status: 0
apps/web/build: absent
apps/web/.svelte-kit: absent

Known gaps: no runtime latency, CPU/RSS, VM/HDD, Apple-client or deploy-storm measurements; no crash, data loss or security exploit demonstrated; no new BLOCKER issue. CardDAV sync is not built at reviewed revisions. Follow-up tests remain with the linked owners.

Decisions: use queued instant-interaction §58/#663 because base has no §58 and round-7a uses that number for discovery; record this without editing DESIGN. Prefer reasoned evidence and a scratch Index over unnecessary builds/VM claims. Reuse existing owners. DAV needs explicit continuation; WebDAV must preserve descriptor/ETag coherence. No product decisions changed.

UX gaps closed: none (no UI changes). UX gaps left: no UI feature was accepted as complete; shared-stream freshness/adoption remains with existing owners.

No push, deploy, issue close or product edit. Final report and report comments contain no secrets or financial data.

Completed perf-arch-sync audit. Built: source-evidence matrix for Mail IMAP, CalDAV client/server, CardDAV status, WebDAV, Notes collaboration, Files SSE, #668 and notifications. File: `audit-findings.md` (only repository change). Branch: `job/perf-arch-sync`. Head: `788ad816af5c0de8ccf007f8fd7321ba9e72286b`. Three atomic documentation commits. Working tree clean. Base `c4a61e8cf`; checked round-7a `2f4482ded`, queued #653 `4723c5f3b`, #668 `7695d237f` and intended performance §58 `e62249ded`. Filed findings, each with context, source locations, reasoned impact, fix and tests: - #753: Mail IDLE uses the three ingest slots and leaves 300 s listener gaps. Concurrent #763 overlaps the slots; cross-linked for one fix owner. - #757: Mail delta drains all new UID windows; expunge loads the complete live set and prunes in one writer transaction. - #769: one external Note hint reads every loaded room for that User before matching the changed path. - #778: one flush timer task per dirty update; narrowed to room scheduling because concurrent #761 owns watcher queue bounds. - #800: WebDAV file metadata and opens wait on the global filesystem mutation lock, including unrelated Users' work. Added evidence to existing owners #573 (DAV bounds), #677 (Calendar read awaits provider refresh), #679 (sync fairness and writer parsing), #668 (adoption, fan-out, idle tick cost and reconnect jitter), and #761 (watcher coordination). Scratch Index evidence: actual Reminder migration/query, 10,000 retained changes for one Task. Indexed seek plan: `SEARCH reminder_changes USING INDEX sqlite_autoindex_reminder_changes_1 (user_id=? AND seq>?)`. Fetch returns 10,000 rows before one UID remains. This proves work count, not latency. No baseline ratio claimed. Verification: requested fetch/merge once before gates; merge output verbatim: ```text Already up to date. ``` Cargo fmt and diff check produced no stdout/stderr. Recorded exit statuses, verbatim: ```text cargo fmt --check exit status: 0 git diff --check exit status: 0 ``` No changed crate or web code: clippy, crate tests, web gates and adversarial traffic not run under this read-mostly job's minimal-build instruction. Cargo env used line-tables-only, incremental=0, build jobs=4 and worktree target/tmp; preset target unchanged. Cleanup output, verbatim: ```text Removed 1 file, 356B total cargo clean exit status: 0 apps/web/build: absent apps/web/.svelte-kit: absent ``` Known gaps: no runtime latency, CPU/RSS, VM/HDD, Apple-client or deploy-storm measurements; no crash, data loss or security exploit demonstrated; no new BLOCKER issue. CardDAV sync is not built at reviewed revisions. Follow-up tests remain with the linked owners. Decisions: use queued instant-interaction §58/#663 because base has no §58 and round-7a uses that number for discovery; record this without editing DESIGN. Prefer reasoned evidence and a scratch Index over unnecessary builds/VM claims. Reuse existing owners. DAV needs explicit continuation; WebDAV must preserve descriptor/ETag coherence. No product decisions changed. UX gaps closed: none (no UI changes). UX gaps left: no UI feature was accepted as complete; shared-stream freshness/adoption remains with existing owners. No push, deploy, issue close or product edit. Final report and report comments contain no secrets or financial data.
Author
Owner

Completed the read-mostly server request architecture audit on job/perf-arch-server.

Built: audit-findings.md with ranked request-family findings, shared middleware costs, protocol traces, the server rules matrix, all 335 base OpenAPI operations, runtime-only surfaces, pending-branch identities and concrete fix/test proposals. Product code, dependencies and contracts are unchanged.

Head: 4aa70a41fda249fbafbe7fe7072396963f72b07c. Atomic commits: 60baf4bde HTTP findings/owners; 0a14d1aed protocol and derived-cache findings; 4aa70a41f route inventory, common costs and verification.

New duplicate-checked issues:

  • #780 — Notes IMAP SELECT/STATUS scans all live Notes under the User writer lock; FETCH headers/size can build full MIME.
  • #782 — Analytics GET waits for optional derived cache writes and repeats cold/current-day computation.
  • #783 — cached HLS response setup holds a global cache lock across SQLite last-access writes.
  • #784 — Tag suggestions build whole-Home namespace object sets; Tag pages use OFFSET.

Extra evidence went to existing #703 (NOCASE title sort/index mismatch), #512 (App Password reads await activity UPDATE), #679 (one subscription-cache query per feed), #681 (Versions takes the global mutation lock; Trash scans metadata), #698 (one control query per job kind; pure Security-state SELECTs on writer_pool). Existing HTTP issues #677–#704 and shared owners #665–#668 are retained. No issues closed.

Base and final origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. The required final fetch/merge output, verbatim:

Already up to date.

Inspected round 7a at 2f4482ded066d9c5d9c59130377907f7fd2916c9. All four new finding signatures remain there. Also checked queued DESIGN §58, #665 Note validators, #667 receipts, #668 stream scope, #653 Journal publication and the paused queue. #665 still reads/parses Note source before conditional_json, so #702 remains required.

Gate output: cargo fmt --check exited 0 with empty stdout/stderr. git diff --check exited 0 with empty stdout/stderr. No changed crate: clippy/test not run under the job's minimal-build rule. No web changes: web gates not run. No API merge/change: live adversarial round not applicable.

Read-only verification output, verbatim:

PASS: 335 declared operations counted; four filed issue links present.
PASS: four finding signatures remain in base and inspected round 7a.
PASS: Notes title SQL plan needs a temporary sort; edited order uses its index.
PASS: only audit-findings.md differs from origin/dev; no product files changed.

Scratch SQLite used the exact Notes schema migration, with no User data. Title query plan, verbatim:

SEARCH note_items USING INDEX sqlite_autoindex_note_items_2 (user_id=?)
USE TEMP B-TREE FOR ORDER BY

Edited-order plan:

SEARCH note_items USING INDEX note_items_recent (user_id=?)

Cleanup output, verbatim:

     Removed 1 file, 356B total
No generated web output: apps/web/build
No generated web output: apps/web/.svelte-kit

Working tree is clean. No push or deploy. Only the explicitly required origin/dev merge command ran; it changed nothing.

Known gaps: code-derived impact ranking, not measured latency/CPU/RSS. No perf-VM run, live protocol tests or per-handler runtime budget proof. The full route register is inventory; deep traces are grouped by shared service. Client rules, optional providers, real Apple checks, new Connected Accounts/MCP Events runtime budgets and dynamic plugin routes need follow-up. No confirmed security blocker was found; this is not a security assurance. Each fix issue specifies regression tests and matching benchmark work.

Decisions: use the queued DESIGN §58 because base DESIGN ends at §57; reuse existing owner issues and file only distinct protocol/auxiliary-plugin work; use source evidence plus EXPLAIN rather than unqualified timing on the busy host. No new product decision was made. UX gaps closed/left: not applicable, no UI changes.

Completed the read-mostly server request architecture audit on `job/perf-arch-server`. Built: `audit-findings.md` with ranked request-family findings, shared middleware costs, protocol traces, the server rules matrix, all 335 base OpenAPI operations, runtime-only surfaces, pending-branch identities and concrete fix/test proposals. Product code, dependencies and contracts are unchanged. Head: `4aa70a41fda249fbafbe7fe7072396963f72b07c`. Atomic commits: `60baf4bde` HTTP findings/owners; `0a14d1aed` protocol and derived-cache findings; `4aa70a41f` route inventory, common costs and verification. New duplicate-checked issues: - #780 — Notes IMAP SELECT/STATUS scans all live Notes under the User writer lock; FETCH headers/size can build full MIME. - #782 — Analytics GET waits for optional derived cache writes and repeats cold/current-day computation. - #783 — cached HLS response setup holds a global cache lock across SQLite last-access writes. - #784 — Tag suggestions build whole-Home namespace object sets; Tag pages use OFFSET. Extra evidence went to existing #703 (NOCASE title sort/index mismatch), #512 (App Password reads await activity UPDATE), #679 (one subscription-cache query per feed), #681 (Versions takes the global mutation lock; Trash scans metadata), #698 (one control query per job kind; pure Security-state SELECTs on writer_pool). Existing HTTP issues #677–#704 and shared owners #665–#668 are retained. No issues closed. Base and final origin/dev: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. The required final fetch/merge output, verbatim: ```text Already up to date. ``` Inspected round 7a at `2f4482ded066d9c5d9c59130377907f7fd2916c9`. All four new finding signatures remain there. Also checked queued DESIGN §58, #665 Note validators, #667 receipts, #668 stream scope, #653 Journal publication and the paused queue. #665 still reads/parses Note source before conditional_json, so #702 remains required. Gate output: `cargo fmt --check` exited 0 with empty stdout/stderr. `git diff --check` exited 0 with empty stdout/stderr. No changed crate: clippy/test not run under the job's minimal-build rule. No web changes: web gates not run. No API merge/change: live adversarial round not applicable. Read-only verification output, verbatim: ```text PASS: 335 declared operations counted; four filed issue links present. PASS: four finding signatures remain in base and inspected round 7a. PASS: Notes title SQL plan needs a temporary sort; edited order uses its index. PASS: only audit-findings.md differs from origin/dev; no product files changed. ``` Scratch SQLite used the exact Notes schema migration, with no User data. Title query plan, verbatim: ```text SEARCH note_items USING INDEX sqlite_autoindex_note_items_2 (user_id=?) USE TEMP B-TREE FOR ORDER BY ``` Edited-order plan: ```text SEARCH note_items USING INDEX note_items_recent (user_id=?) ``` Cleanup output, verbatim: ```text Removed 1 file, 356B total No generated web output: apps/web/build No generated web output: apps/web/.svelte-kit ``` Working tree is clean. No push or deploy. Only the explicitly required origin/dev merge command ran; it changed nothing. Known gaps: code-derived impact ranking, not measured latency/CPU/RSS. No perf-VM run, live protocol tests or per-handler runtime budget proof. The full route register is inventory; deep traces are grouped by shared service. Client rules, optional providers, real Apple checks, new Connected Accounts/MCP Events runtime budgets and dynamic plugin routes need follow-up. No confirmed security blocker was found; this is not a security assurance. Each fix issue specifies regression tests and matching benchmark work. Decisions: use the queued DESIGN §58 because base DESIGN ends at §57; reuse existing owner issues and file only distinct protocol/auxiliary-plugin work; use source evidence plus EXPLAIN rather than unqualified timing on the busy host. No new product decision was made. UX gaps closed/left: not applicable, no UI changes.
Author
Owner

Final sec-supplychain report. Branch: job/sec-supplychain. Head: 67fe7158870a623e4071f4977c833e4abba5b958. Base and final origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Built: a read-only audit report. Committed file: audit-findings.md only. Commits: a27858ad8, 2fbbc0853, 67fe71588. Product code, locks and tests are unchanged. Worktree is clean. No push or deploy. The required origin/dev merge returned Already up to date.

Filed findings: #810 JavaScript advisories; #811 Rust advisory/maintenance debt; #812 repeatable advisory/licence checks; #813 pinned build inputs. No merge-blocking product vulnerability was proved. All four findings remain open. UX gaps closed/left: not applicable; no UI change. No benchmark: no user-facing feature, route or background job changed.

The report below includes evidence, files, primary sources, gate output verbatim, known gaps and decisions.

Dependency and supply-chain audit

This report records the sec-supplychain job assigned under #663. It changes no product code.

Scope and decision

  • Audit date: 2026-10-02 (host date).
  • Branch: job/sec-supplychain. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev).
  • Read CLAUDE.md, CONTEXT.md and DESIGN.md. DESIGN ends at §57 on this base. Issue #663 describes performance. The queued job/instant-663 adds §58, also about performance. Follow the explicit security audit brief; do not change the performance issue or the product design.
  • Use CLAUDE.md security and licence rules and DESIGN §§2, 3, 12, 19, 28 and 36. A version match alone does not prove a product security hole.
  • Review root, auth, fuzz and embedding-bench Cargo locks; root and adversarial Bun locks; package licences; vendor patches; build inputs; model downloads; and the queued branch changes below.
  • No push, deployment, API edit or performance measurement. No exploitation test was needed to establish the dependency and build-input findings.

Findings and issues

No merge-blocking product vulnerability was proved in this audit. The following maintenance findings have separate issues. Duplicate searches covered all issue states and the terms dependency, advisory, licence, audit, supply chain, reproducibility, checksum, Tiptap, PDF.js and lru.

Finding Evidence Impact and fix Issue
Known JavaScript advisories remain locked apps/web/package.json:79,121, packages/editor/package.json:56, packages/ui/package.json:28, bun.lock Update the Tiptap family, PDF.js and worker, sharp and nested SvelteKit cookie. Preserve CSP. Run the frozen-lock audit and web/editor/PDF checks. Reachability limits are below. #810
Rust advisory and maintenance debt Cargo.lock:4476,6283, crates/calternal-search/Cargo.toml:32; offline locked metadata and source Track a patched lru through Tantivy. Document a narrow RSA exception for public verification. Upgrade or track the parents of five unmaintained crates. Audit all four locks. #811
No repeatable advisory/licence policy .forgejo/workflows/ci.yml:26-56, weekly workflow; no tracked deny policy Add one small locked-dependency check and scheduled refresh. Record database revision, licence-file clarifications and scoped exception expiry. #812
Build inputs remain mutable .forgejo/workflows/ci.yml:16,18,23, Containerfile:1,13,16,24-26, deploy/Containerfile.runtime:4,125 Pin action commits and image index digests. Record dated OS package inputs and explicit security updates. Compare resolved inputs in clean builds. #813

JavaScript advisory assessment

bun audit --json exited 1 and returned six advisories in four package groups:

Locked package Advisory Fixed version Product assessment
@tiptap/core@3.27.1 GHSA-cp6q-959q-f8rh 3.30.4 image.ts:92 and callout.ts:42 call mergeAttributes with declared schema attributes. No arbitrary attribute-object boundary was proved.
@tiptap/core@3.27.1 GHSA-j95f-988m-3j2f 3.30.5 The app uses packages/editor/src/markdown.ts, not Tiptap's affected Markdown-spec helpers.
pdfjs-dist@5.7.284 GHSA-hq66-cqwq-w95j 6.2.108 PdfView.svelte:36 loads PDFs, but the canvas-only component does not create PDFScriptingManager. The enforced production CSP at security.rs:121 has no unsafe script allowance; upstream lists CSP as a mitigation.
sharp@0.34.5 GHSA-f88m-g3jw-g9cj 0.35.0 Only icon and contact-sheet scripts call sharp. No server upload route uses this npm package.
sharp@0.34.5 GHSA-rgj7-g3m4-5g8c 0.35.4 Same build-tool limit. Do not infer the runtime libheif version from sharp's embedded libraries.
@sveltejs/kit/cookie@0.6.0 GHSA-pxg6-pf52-xh8x 0.7.0 The root cookie is 2.0.1. The product uses an adapter-static SPA and does not run SvelteKit's Node server.

Verified current npm versions: Tiptap core 3.31.4, PDF.js 6.3.289, sharp 0.35.5 and SvelteKit 3.0.0. These do not authorize unrelated major migrations. The issues specify minimum patched releases and compatible updates.

Primary sources: Tiptap attributes, Tiptap Markdown, PDF.js, sharp/libvips, sharp/libheif.

The separate tests/adversarial/bun.lock audit exited 0 and returned {}.

Rust advisory assessment

cargo-audit is not installed. Use a read-only comparison with RustSec database revision 117edb3bed98e9be112f277b7615eea3252e7c43. Parse each advisory's TOML block, exclude withdrawn records and test each locked version against patched/unaffected ranges with Bun's semver matcher. This is not a cargo-audit run. Inspect the matched records and cached dependency source before assigning severity.

Package and root-lock path Match Assessment
lru 0.16.4 ← Tantivy 0.26.2 RUSTSEC-2026-0253; fixed ≥0.18.2 Tantivy src/store/reader.rs:74 stores integer keys. The advisory requires a panicking key destructor during pop and continued use after unwind. That condition was not found.
rsa 0.9.10 ← openidconnect and superboring/web-push RUSTSEC-2023-0071; no patched release OIDC production code verifies public signatures. Private RSA generation is in auth tests. Notifications use EC VAPID. No production RSA decryption oracle was found.
smallstr 0.3.1 ← yrs RUSTSEC-2026-0215 Unmaintained dependency; track its parent.
async-std 1.13.2 ← async-imap dev dependency RUSTSEC-2025-0052 Tokio is the vendor default and Mail runtime. Remove unused alternate-runtime test dependency if possible.
paste 1.0.15 ← tokenizers RUSTSEC-2024-0436 Unmaintained dependency; track its parent.
ttf-parser 0.25.1 ← lopdf RUSTSEC-2026-0192 Unmaintained dependency; track its parent.
fxhash 0.2.1 ← selectors RUSTSEC-2025-0057 Unmaintained dependency; track its parent.

The fuzz lock repeats all matches except async-std. The embedding bench lock matches paste. The nested auth lock matches rsa. cargo search verified Tantivy 0.26.2, lru 0.18.5 and cargo-audit 0.22.2. A cross-major lru lock bump cannot satisfy Tantivy's current requirement by itself.

Sources: lru advisory, RSA advisory and the pinned RustSec records for the five unmaintained crates.

Licences and integrity

  • Locked Cargo metadata contains 861 packages: 28 product crates, one vendored dependency and 832 registry packages. All product crates declare AGPL-3.0-only. All seven product/test package.json files declare AGPL-3.0-only.
  • Inspected npm registry metadata for 743 exact locked releases, including optional platform releases and multiple locked versions. All release integrity values match the Bun lock. None of these release records reports deprecation.
  • No solely GPL-2.0-only, proprietary or non-commercial dependency licence was identified in the inspected metadata and licence files. This is an inventory result, not certification of all redistribution obligations.
  • self_cell 1.3.0 offers Apache-2.0 OR GPL-2.0-only. Select the Apache alternative. Do not treat an OR expression as an unconditional GPL-2.0-only dependency.
  • nom-exif 3.8.0 has no SPDX field, but its licence-file is MIT. svelte-toolbelt 0.10.6 also has no SPDX field; its integrity-verified tarball includes an MIT licence. Neither is an unlicensed-package finding.
  • Keep MPL/LGPL notices, the timezone data's MIT AND ODbL-1.0 terms (utz_data_balanced), Unicode notices and CDLA-Permissive-2.0 root-certificate data terms. Distribution notice completeness was not proved by this metadata review.
  • The independent warm tooltip documents why it does not copy sveltebits' MIT + Commons Clause source (packages/ui/src/components/tooltip/warmth.ts:11; DESIGN §34). The inspected drag-and-drop and Bklit vendor trees include MIT licence files and source provenance.
  • Root Cargo registry sources all have checksums. No moving Git dependency was found in the reviewed root lock. Manifest semver ranges are not a reproducibility hole when builds enforce the committed locks.

Vendor review

Compared async-imap 0.11.3 to the cached crates.io archive, SHA-256 9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570. Both upstream licence files match the vendor copies.

Reviewed all differing source files: client.rs, imap_stream.rs, parse.rs, mock_stream.rs and types/fetch.rs, plus Cargo.toml. The patches remove wire logs, redact parser errors, cap response buffers at 8 MiB, expose pre-auth CAPABILITY, preserve tagged FETCH failures, expose Gmail thread IDs and apply one test helper modernization. No unexpected executable/build-script addition was found. CODEX_PATCHES.md describes the security patches; it omits the small Gmail accessor and test helper change. Record these during the next vendor refresh.

Known test mismatch #625 is fixed in queued job/imaptest-625: the parsing-error test expects the fixed redacted message. The audit did not change that test expectation or repeat that issue.

Model and native downloads

  • The committed embedding manifest pins MiniLM and CLIP repository revisions and seven SHA-256 hashes. Download code checks size bounds and the final hash before calternal-fs writes assets. Installed files are hashed again before inference. Hostnames and file names come from the committed manifest, not User input.
  • Hugging Face API metadata at both pinned revisions matches the sizes and LFS SHA-256 values for all five ONNX graphs. Independently fetched both small tokenizer files and verified their SHA-256 and size. No model graph was downloaded for this audit.
  • The pinned MiniLM model card declares apache-2.0. CLIP's conversion card has no licence field; the manifest points to the upstream OpenAI CLIP MIT licence. The upstream licence was read. Keep the conversion provenance and upstream notice; do not infer a new licence from an absent card field.
  • No runtime InsightFace or non-commercial face-weight downloader was found in the reviewed code. Faces remain subject to #31 and DESIGN §§12, 28.
  • ort-sys 2.0.0-rc.13 uses a distribution table with hashes. Its build downloads and extracts into a temporary directory, checks SHA-256, then publishes the cache directory. An existing cache directory is trusted without rehashing. Record this trusted build-cache boundary; a compromised local build cache is outside the remote download check.
  • deploy/Containerfile.runtime verifies libde265 1.1.3, libheif 1.23.5 and libvips 8.16.1 source hashes. These pins do not audit every native CVE or freeze apt dependencies. The reviewed libheif advisory GHSA-g89c-p67h-r497 is fixed in 1.23.2, below the pinned 1.23.5. No broad claim that all native codecs are current is made.
  • Official Agent tools are downloaded on demand under DESIGN §19. The installer resolves latest, checks npm tarball SHA-512 and registry signatures, and verifies the selected release's lock integrity. It runs lifecycle code only after verification. The installer has no User Home/token mount; User containers see the tools volume read-only (runtime.rs:91-106,181). This is an explicit vendor-update trust boundary, not a bundled proprietary dependency. It is not a fully pinned, offline tool installation.

Queued branch coverage

Reviewed changed audit inputs without merging queued jobs. Round-7a adds only one external Cargo release: standardwebhooks 1.0.1. crates.io reports MIT and the checksum matches its new lock entry. Its model change adds lazy shared initialization; it preserves the manifest, bounded downloads and hash checks. Its web manifest change adds a test command only.

The round-7b list was read from /home/kayg/.local/state/codex-jobs/calternal/paused-queue.txt. Every listed root lock has the same seven RustSec matches. None changes the root Bun lock or model manifest. voicefiles adds only the dependency-free AGPL calternal-media crate. blaze-settings adds test scripts; imaptest changes the known privacy test. The remaining changed Cargo manifests reuse already locked releases.

Branch Audited SHA
origin/job/merge-round-7a 2f4482ded066d9c5d9c59130377907f7fd2916c9
job/blaze-settings bf3ad5f29d1108c34e87c0e9d20b1dbe67ffc5c3
job/instant-663 e62249dedcc2c7d108e4432596d40aee6f5a4bc8
job/writeonopen-661 04c4a651be5a0da6c1311af9ad2d39bd289b8a09
job/ryw-653 4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63
job/admin-burst-705 23a6fe0e0e326f789c886f366880f5b86683b287
job/voicefiles-620 b7ef7a2ab57f45b5d46cd19b4560215acae918e3
job/perf-cache-665 b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2
job/fix-499 242022301673dc6746d89985ee36078743723591
job/perf-snap-666 253c2a00cade24a7f845a5e67f309093641b8850
job/calimg-589 421dd63735d116cba4961a0a3ca4c985baa83480
job/imaptest-625 f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3
job/perf-mut-667 52d2b17f805072cd0304d7a05fe0534523cc7bc3
job/burst-709 c421756ac9af5a9b653b7c9f53c41b3aca24de89

Known gaps

  • No full cargo-audit executable run. The custom comparison does not audit Rust standard-library or Cargo tooling advisories and is not a replacement for #812.
  • No licence review of every upstream bundled native asset or release notice pack. No clean double build, offline rebuild or bit-identical binary proof.
  • No live attack, browser exploit reproduction or API adversarial round. No API was changed, and version matches did not establish a reachable product exploit.
  • No UI changes: screenshots, UX gaps and hot-path benchmark profiles are not applicable.
  • The four filed issues remain open. No package, lock or product code was changed.
  • Audit artifacts remain under artifacts/sec-supplychain/; they are not committed. The report includes database, source and branch revisions so another audit can repeat the checks.

Verification

The required git fetch origin && git merge origin/dev completed. The merge output was:

Already up to date.

cargo fmt --check emitted no stdout/stderr and exited 0. The status wrapper and evidence checks emitted:

cargo fmt --check exit: 0
PASS: 832 Cargo registry checksums; no Git sources; crate licences; 14 branch snapshots; four issue links; report limits.
git diff --check exit: 0
PASS: vendored async-imap upstream archive checksum matches crates.io metadata.

cargo clean used the preset job directory /mnt/hdd/targets/jobs/sec-supplychain. Output:

     Removed 1 file, 1.2KiB total
cargo clean exit: 0

No web dependencies or build output were created. Rust clippy/test and web check/test were not run: this audit changes only Markdown and the job requires minimal builds. Do not read this as a passing product gate report. The root Bun audit remains exit 1 (six recorded advisories); the separate adversarial lock audit is exit 0 ({}).

Decisions

Follow the explicit supply-chain brief despite the #663/§58 performance mismatch. Use a read-only RustSec comparison because cargo-audit is absent; record that limit instead of compiling an audit tool on the shared host. Classify matches from affected call sites and conditions, not severity labels alone. Treat DESIGN §19's signed on-demand vendor tooling as an explicit update boundary. Do not change product code in this audit.

Final sec-supplychain report. Branch: `job/sec-supplychain`. Head: `67fe7158870a623e4071f4977c833e4abba5b958`. Base and final origin/dev: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Built: a read-only audit report. Committed file: `audit-findings.md` only. Commits: `a27858ad8`, `2fbbc0853`, `67fe71588`. Product code, locks and tests are unchanged. Worktree is clean. No push or deploy. The required origin/dev merge returned `Already up to date.` Filed findings: #810 JavaScript advisories; #811 Rust advisory/maintenance debt; #812 repeatable advisory/licence checks; #813 pinned build inputs. No merge-blocking product vulnerability was proved. All four findings remain open. UX gaps closed/left: not applicable; no UI change. No benchmark: no user-facing feature, route or background job changed. The report below includes evidence, files, primary sources, gate output verbatim, known gaps and decisions. # Dependency and supply-chain audit This report records the sec-supplychain job assigned under #663. It changes no product code. ## Scope and decision - Audit date: 2026-10-02 (host date). - Branch: `job/sec-supplychain`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). - Read CLAUDE.md, CONTEXT.md and DESIGN.md. DESIGN ends at §57 on this base. Issue #663 describes performance. The queued `job/instant-663` adds §58, also about performance. Follow the explicit security audit brief; do not change the performance issue or the product design. - Use CLAUDE.md security and licence rules and DESIGN §§2, 3, 12, 19, 28 and 36. A version match alone does not prove a product security hole. - Review root, auth, fuzz and embedding-bench Cargo locks; root and adversarial Bun locks; package licences; vendor patches; build inputs; model downloads; and the queued branch changes below. - No push, deployment, API edit or performance measurement. No exploitation test was needed to establish the dependency and build-input findings. ## Findings and issues No merge-blocking product vulnerability was proved in this audit. The following maintenance findings have separate issues. Duplicate searches covered all issue states and the terms dependency, advisory, licence, audit, supply chain, reproducibility, checksum, Tiptap, PDF.js and lru. | Finding | Evidence | Impact and fix | Issue | | --- | --- | --- | --- | | Known JavaScript advisories remain locked | `apps/web/package.json:79,121`, `packages/editor/package.json:56`, `packages/ui/package.json:28`, `bun.lock` | Update the Tiptap family, PDF.js and worker, sharp and nested SvelteKit cookie. Preserve CSP. Run the frozen-lock audit and web/editor/PDF checks. Reachability limits are below. | [#810](https://git.kayg.org/kayg/calternal/issues/810) | | Rust advisory and maintenance debt | `Cargo.lock:4476,6283`, `crates/calternal-search/Cargo.toml:32`; offline locked metadata and source | Track a patched lru through Tantivy. Document a narrow RSA exception for public verification. Upgrade or track the parents of five unmaintained crates. Audit all four locks. | [#811](https://git.kayg.org/kayg/calternal/issues/811) | | No repeatable advisory/licence policy | `.forgejo/workflows/ci.yml:26-56`, weekly workflow; no tracked deny policy | Add one small locked-dependency check and scheduled refresh. Record database revision, licence-file clarifications and scoped exception expiry. | [#812](https://git.kayg.org/kayg/calternal/issues/812) | | Build inputs remain mutable | `.forgejo/workflows/ci.yml:16,18,23`, `Containerfile:1,13,16,24-26`, `deploy/Containerfile.runtime:4,125` | Pin action commits and image index digests. Record dated OS package inputs and explicit security updates. Compare resolved inputs in clean builds. | [#813](https://git.kayg.org/kayg/calternal/issues/813) | ### JavaScript advisory assessment `bun audit --json` exited 1 and returned six advisories in four package groups: | Locked package | Advisory | Fixed version | Product assessment | | --- | --- | --- | --- | | `@tiptap/core@3.27.1` | GHSA-cp6q-959q-f8rh | 3.30.4 | `image.ts:92` and `callout.ts:42` call mergeAttributes with declared schema attributes. No arbitrary attribute-object boundary was proved. | | `@tiptap/core@3.27.1` | GHSA-j95f-988m-3j2f | 3.30.5 | The app uses `packages/editor/src/markdown.ts`, not Tiptap's affected Markdown-spec helpers. | | `pdfjs-dist@5.7.284` | GHSA-hq66-cqwq-w95j | 6.2.108 | `PdfView.svelte:36` loads PDFs, but the canvas-only component does not create PDFScriptingManager. The enforced production CSP at `security.rs:121` has no unsafe script allowance; upstream lists CSP as a mitigation. | | `sharp@0.34.5` | GHSA-f88m-g3jw-g9cj | 0.35.0 | Only icon and contact-sheet scripts call sharp. No server upload route uses this npm package. | | `sharp@0.34.5` | GHSA-rgj7-g3m4-5g8c | 0.35.4 | Same build-tool limit. Do not infer the runtime libheif version from sharp's embedded libraries. | | `@sveltejs/kit/cookie@0.6.0` | GHSA-pxg6-pf52-xh8x | 0.7.0 | The root cookie is 2.0.1. The product uses an adapter-static SPA and does not run SvelteKit's Node server. | Verified current npm versions: Tiptap core 3.31.4, PDF.js 6.3.289, sharp 0.35.5 and SvelteKit 3.0.0. These do not authorize unrelated major migrations. The issues specify minimum patched releases and compatible updates. Primary sources: [Tiptap attributes](https://github.com/ueberdosis/tiptap/security/advisories/GHSA-cp6q-959q-f8rh), [Tiptap Markdown](https://github.com/ueberdosis/tiptap/security/advisories/GHSA-j95f-988m-3j2f), [PDF.js](https://github.com/mozilla/pdf.js/security/advisories/GHSA-hq66-cqwq-w95j), [sharp/libvips](https://github.com/lovell/sharp/security/advisories/GHSA-f88m-g3jw-g9cj), [sharp/libheif](https://github.com/lovell/sharp/security/advisories/GHSA-rgj7-g3m4-5g8c). The separate `tests/adversarial/bun.lock` audit exited 0 and returned `{}`. ### Rust advisory assessment cargo-audit is not installed. Use a read-only comparison with RustSec database revision `117edb3bed98e9be112f277b7615eea3252e7c43`. Parse each advisory's TOML block, exclude withdrawn records and test each locked version against patched/unaffected ranges with Bun's semver matcher. This is not a cargo-audit run. Inspect the matched records and cached dependency source before assigning severity. | Package and root-lock path | Match | Assessment | | --- | --- | --- | | lru 0.16.4 ← Tantivy 0.26.2 | RUSTSEC-2026-0253; fixed ≥0.18.2 | Tantivy `src/store/reader.rs:74` stores integer keys. The advisory requires a panicking key destructor during pop and continued use after unwind. That condition was not found. | | rsa 0.9.10 ← openidconnect and superboring/web-push | RUSTSEC-2023-0071; no patched release | OIDC production code verifies public signatures. Private RSA generation is in auth tests. Notifications use EC VAPID. No production RSA decryption oracle was found. | | smallstr 0.3.1 ← yrs | RUSTSEC-2026-0215 | Unmaintained dependency; track its parent. | | async-std 1.13.2 ← async-imap dev dependency | RUSTSEC-2025-0052 | Tokio is the vendor default and Mail runtime. Remove unused alternate-runtime test dependency if possible. | | paste 1.0.15 ← tokenizers | RUSTSEC-2024-0436 | Unmaintained dependency; track its parent. | | ttf-parser 0.25.1 ← lopdf | RUSTSEC-2026-0192 | Unmaintained dependency; track its parent. | | fxhash 0.2.1 ← selectors | RUSTSEC-2025-0057 | Unmaintained dependency; track its parent. | The fuzz lock repeats all matches except async-std. The embedding bench lock matches paste. The nested auth lock matches rsa. `cargo search` verified Tantivy 0.26.2, lru 0.18.5 and cargo-audit 0.22.2. A cross-major lru lock bump cannot satisfy Tantivy's current requirement by itself. Sources: [lru advisory](https://rustsec.org/advisories/RUSTSEC-2026-0253.html), [RSA advisory](https://rustsec.org/advisories/RUSTSEC-2023-0071.html) and the pinned RustSec records for the five unmaintained crates. ## Licences and integrity - Locked Cargo metadata contains 861 packages: 28 product crates, one vendored dependency and 832 registry packages. All product crates declare AGPL-3.0-only. All seven product/test package.json files declare AGPL-3.0-only. - Inspected npm registry metadata for 743 exact locked releases, including optional platform releases and multiple locked versions. All release integrity values match the Bun lock. None of these release records reports deprecation. - No solely GPL-2.0-only, proprietary or non-commercial dependency licence was identified in the inspected metadata and licence files. This is an inventory result, not certification of all redistribution obligations. - self_cell 1.3.0 offers Apache-2.0 OR GPL-2.0-only. Select the Apache alternative. Do not treat an OR expression as an unconditional GPL-2.0-only dependency. - nom-exif 3.8.0 has no SPDX field, but its licence-file is MIT. svelte-toolbelt 0.10.6 also has no SPDX field; its integrity-verified tarball includes an MIT licence. Neither is an unlicensed-package finding. - Keep MPL/LGPL notices, the timezone data's `MIT AND ODbL-1.0` terms (`utz_data_balanced`), Unicode notices and CDLA-Permissive-2.0 root-certificate data terms. Distribution notice completeness was not proved by this metadata review. - The independent warm tooltip documents why it does not copy sveltebits' MIT + Commons Clause source (`packages/ui/src/components/tooltip/warmth.ts:11`; DESIGN §34). The inspected drag-and-drop and Bklit vendor trees include MIT licence files and source provenance. - Root Cargo registry sources all have checksums. No moving Git dependency was found in the reviewed root lock. Manifest semver ranges are not a reproducibility hole when builds enforce the committed locks. ## Vendor review Compared async-imap 0.11.3 to the cached crates.io archive, SHA-256 `9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570`. Both upstream licence files match the vendor copies. Reviewed all differing source files: `client.rs`, `imap_stream.rs`, `parse.rs`, `mock_stream.rs` and `types/fetch.rs`, plus Cargo.toml. The patches remove wire logs, redact parser errors, cap response buffers at 8 MiB, expose pre-auth CAPABILITY, preserve tagged FETCH failures, expose Gmail thread IDs and apply one test helper modernization. No unexpected executable/build-script addition was found. CODEX_PATCHES.md describes the security patches; it omits the small Gmail accessor and test helper change. Record these during the next vendor refresh. Known test mismatch #625 is fixed in queued `job/imaptest-625`: the parsing-error test expects the fixed redacted message. The audit did not change that test expectation or repeat that issue. ## Model and native downloads - The committed embedding manifest pins MiniLM and CLIP repository revisions and seven SHA-256 hashes. Download code checks size bounds and the final hash before `calternal-fs` writes assets. Installed files are hashed again before inference. Hostnames and file names come from the committed manifest, not User input. - Hugging Face API metadata at both pinned revisions matches the sizes and LFS SHA-256 values for all five ONNX graphs. Independently fetched both small tokenizer files and verified their SHA-256 and size. No model graph was downloaded for this audit. - The pinned MiniLM model card declares apache-2.0. CLIP's conversion card has no licence field; the manifest points to the upstream OpenAI CLIP MIT licence. The upstream licence was read. Keep the conversion provenance and upstream notice; do not infer a new licence from an absent card field. - No runtime InsightFace or non-commercial face-weight downloader was found in the reviewed code. Faces remain subject to #31 and DESIGN §§12, 28. - ort-sys 2.0.0-rc.13 uses a distribution table with hashes. Its build downloads and extracts into a temporary directory, checks SHA-256, then publishes the cache directory. An existing cache directory is trusted without rehashing. Record this trusted build-cache boundary; a compromised local build cache is outside the remote download check. - `deploy/Containerfile.runtime` verifies libde265 1.1.3, libheif 1.23.5 and libvips 8.16.1 source hashes. These pins do not audit every native CVE or freeze apt dependencies. The reviewed libheif advisory GHSA-g89c-p67h-r497 is fixed in 1.23.2, below the pinned 1.23.5. No broad claim that all native codecs are current is made. - Official Agent tools are downloaded on demand under DESIGN §19. The installer resolves latest, checks npm tarball SHA-512 and registry signatures, and verifies the selected release's lock integrity. It runs lifecycle code only after verification. The installer has no User Home/token mount; User containers see the tools volume read-only (`runtime.rs:91-106,181`). This is an explicit vendor-update trust boundary, not a bundled proprietary dependency. It is not a fully pinned, offline tool installation. ## Queued branch coverage Reviewed changed audit inputs without merging queued jobs. Round-7a adds only one external Cargo release: standardwebhooks 1.0.1. crates.io reports MIT and the checksum matches its new lock entry. Its model change adds lazy shared initialization; it preserves the manifest, bounded downloads and hash checks. Its web manifest change adds a test command only. The round-7b list was read from `/home/kayg/.local/state/codex-jobs/calternal/paused-queue.txt`. Every listed root lock has the same seven RustSec matches. None changes the root Bun lock or model manifest. voicefiles adds only the dependency-free AGPL calternal-media crate. blaze-settings adds test scripts; imaptest changes the known privacy test. The remaining changed Cargo manifests reuse already locked releases. | Branch | Audited SHA | | --- | --- | | `origin/job/merge-round-7a` | `2f4482ded066d9c5d9c59130377907f7fd2916c9` | | `job/blaze-settings` | `bf3ad5f29d1108c34e87c0e9d20b1dbe67ffc5c3` | | `job/instant-663` | `e62249dedcc2c7d108e4432596d40aee6f5a4bc8` | | `job/writeonopen-661` | `04c4a651be5a0da6c1311af9ad2d39bd289b8a09` | | `job/ryw-653` | `4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63` | | `job/admin-burst-705` | `23a6fe0e0e326f789c886f366880f5b86683b287` | | `job/voicefiles-620` | `b7ef7a2ab57f45b5d46cd19b4560215acae918e3` | | `job/perf-cache-665` | `b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2` | | `job/fix-499` | `242022301673dc6746d89985ee36078743723591` | | `job/perf-snap-666` | `253c2a00cade24a7f845a5e67f309093641b8850` | | `job/calimg-589` | `421dd63735d116cba4961a0a3ca4c985baa83480` | | `job/imaptest-625` | `f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3` | | `job/perf-mut-667` | `52d2b17f805072cd0304d7a05fe0534523cc7bc3` | | `job/burst-709` | `c421756ac9af5a9b653b7c9f53c41b3aca24de89` | ## Known gaps - No full cargo-audit executable run. The custom comparison does not audit Rust standard-library or Cargo tooling advisories and is not a replacement for #812. - No licence review of every upstream bundled native asset or release notice pack. No clean double build, offline rebuild or bit-identical binary proof. - No live attack, browser exploit reproduction or API adversarial round. No API was changed, and version matches did not establish a reachable product exploit. - No UI changes: screenshots, UX gaps and hot-path benchmark profiles are not applicable. - The four filed issues remain open. No package, lock or product code was changed. - Audit artifacts remain under `artifacts/sec-supplychain/`; they are not committed. The report includes database, source and branch revisions so another audit can repeat the checks. ## Verification The required `git fetch origin && git merge origin/dev` completed. The merge output was: ```text Already up to date. ``` `cargo fmt --check` emitted no stdout/stderr and exited 0. The status wrapper and evidence checks emitted: ```text cargo fmt --check exit: 0 PASS: 832 Cargo registry checksums; no Git sources; crate licences; 14 branch snapshots; four issue links; report limits. git diff --check exit: 0 PASS: vendored async-imap upstream archive checksum matches crates.io metadata. ``` `cargo clean` used the preset job directory `/mnt/hdd/targets/jobs/sec-supplychain`. Output: ```text Removed 1 file, 1.2KiB total cargo clean exit: 0 ``` No web dependencies or build output were created. Rust clippy/test and web check/test were not run: this audit changes only Markdown and the job requires minimal builds. Do not read this as a passing product gate report. The root Bun audit remains exit 1 (six recorded advisories); the separate adversarial lock audit is exit 0 (`{}`). ## Decisions Follow the explicit supply-chain brief despite the #663/§58 performance mismatch. Use a read-only RustSec comparison because cargo-audit is absent; record that limit instead of compiling an audit tool on the shared host. Classify matches from affected call sites and conditions, not severity labels alone. Treat DESIGN §19's signed on-demand vendor tooling as an explicit update boundary. Do not change product code in this audit.
Author
Owner

Completed bundle/loading audit on job/perf-arch-bundle. Head: e8a263c3e9dda7b9a009b197a5255c17c66c98ba.

Built a dependency-free production manifest analyzer and three accounting tests. Files: audit-findings.md, bench/bundle-audit.mjs, bench/bundle-audit.test.mjs. No product edits, pushes or deployments. Fetch/merge of origin/dev was already up to date. New issues: #803 compression, #804 font caching, #805 PDF first-page loading. Evidence posted to existing #497, #642 and #672. No blocker confirmed.

Production build and three targeted analyzer tests pass; syntax and whitespace checks pass. Rust/web source gates and adversarial API tests are not applicable to this audit-only change. Cargo clean and generated web-output cleanup were requested at completion. UX gaps closed: none (audit only). UX gaps left and decisions are in the report below.

Bundle and loading audit — #663

This report checks the production web build and its loading paths. It does
not change the product. All size measurements below use the same source
revision. A source check and a timed browser run are different evidence.

Scope and method

  • Base: origin/dev, c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
  • Branch: job/perf-arch-bundle. Audit date: 2026-10-02.
  • Read CLAUDE.md, CONTEXT.md and DESIGN before the audit.
  • DESIGN §58 is absent on the base. Round 7a uses §58 for agent discovery
    (#630). job/instant-663 supplies the instant-interaction rules under the
    same number. These doc changes need reconciliation at merge. This audit
    uses #663, DESIGN §§18, 38, 44 and the performance text on job/instant-663.
  • Installed with bun install --frozen-lockfile; no dependency changes.
    Registry checks with bun pm view <package>@<version> version returned
    Vite 8.3.0, SvelteKit 2.70.3 and Svelte 5.57.1.
  • Ran one local production build with bun run build in apps/web.
    No Rust build, deployment or perf VM run was needed for byte counts.
  • node bench/bundle-audit.mjs reads the production Vite manifest and Kit's
    optimized route dictionary. It follows static imports only. It counts
    shared JS and CSS once per route. Gzip is applied to each file separately.
  • The shell includes entry/start, entry/app, node 0 and node 1. Kit
    src/runtime/client/client.js:365–369 loads both root nodes eagerly.
  • Redirect rows describe only the resolver route. For /today, add the
    Calendar destination; for /n/<id> and /t/<id>, add the Note destination.
    Optional imports after mount, fonts, images and API responses are not in
    the static route totals. These totals do not prove time to first usable view.
  • artifacts/build.log and artifacts/bundles.json hold local raw evidence.
    The analyzer reproduces the counts after a build. No review images are
    committed. This audit makes no visual-quality claim.

Findings and owners

No merge-blocking security finding was confirmed in this audit.
Performance findings do not block a merge.

Finding Evidence and impact Owner
Required shell exceeds the existing initial-JS budget 369,672 B gzip JS, before route-specific code; budget is less than 200,000 B. All routes inherit this cost. Existing #497; evidence posted
Settings loads every section Settings adds 82,857 B gzip JS to the shell. Static imports at apps/web/src/routes/settings/[...path]/+page.svelte:44–56 include Admin even for a User page. Existing #642, #556; #642 evidence posted
Public file links reach the guest editor apps/web/src/lib/files/PublicLinkPage.svelte:36 imports NoteEditorSurface; its use at :603 is conditional. A file gallery still has a 606,717 B gzip JS route closure. #497; evidence posted; coordinate with DESIGN §54 view-only links
Direct static delivery has no compression crates/calternal-server/src/main.rs:1074–1096,1131–1160 sends raw embedded bytes. No compression layer or encoding negotiation exists. Shell JS/CSS total 1,432,913 B raw versus 430,205 B gzip. Private Traefik configuration was not inspected. New #803
Stable font URLs cannot get a conditional 304 /fonts/*.woff2 gets no-cache, without ETag or Last-Modified, from the same asset path. Default Latin UI/display faces total 147,640 B. Repeated document loads need fresh font transfers from this server. New #804
PDF first page waits for all page sizes packages/ui/src/components/viewer/PdfView.svelte:46–52 awaits getPage for every page before publishing any canvases. Work before first page grows with total pages. This is reasoned impact, not measured latency. New #805; coordinate with #741 text accessibility
Mail primary content waits for ancillary reads apps/web/src/lib/mail/MailView.svelte:324–380 awaits accounts, folders and preferences before list/detail. A thread also awaits attachment metadata before its first body. Existing #672, #640; #672 evidence posted

Duplicate searches used open and all issue searches for bundle, compression,
gzip, WOFF2, font cache and PDF. The three new issues each include source
revision, file:line, impact, a concrete fix and a test. Existing #497, #642,
#556, #640 and #672 keep their scope. #234/#322 own font choices;
#547 owns server PDF thumbnails; #741 owns PDF accessibility. They do not
cover the new delivery or first-page findings.

Production route sizes

All numbers are bytes. JS raw is emitted, minified code before compression.
JS delta is the static closure added to the shared shell. A zero-data route
can still run further imports when it mounts. Query parameters do not create
another code split. The catch-all Settings row covers User and Admin pages.

Shared shell: 1,091,341 B raw JS / 369,672 B gzip JS, 77 JS files.
CSS: 341,572 B raw / 60,533 B gzip, 5 CSS files.

Kit route JS raw JS gzip CSS gzip JS delta
/ 1,091,820 370,044 60,533 372
/admin/[...page] 1,091,747 370,011 60,533 339
/ai/turns/[id] 1,091,986 370,082 60,533 410
/analytics 1,091,804 370,028 60,533 356
/analytics/[period] 1,091,841 370,049 60,533 377
/analytics/[period]/[date] 1,099,919 373,130 60,837 3,458
/ask 1,100,371 373,630 61,717 3,958
/calendar 1,091,753 369,989 60,533 317
/calendar/[view] 1,091,790 370,029 60,533 357
/calendar/[view]/[date] 1,148,205 389,945 61,409 20,273
/d/[date] 1,092,448 370,339 60,658 667
/e/[id] 1,092,458 370,310 60,533 638
/files 1,161,750 394,095 63,403 24,423
/files/recent 1,103,912 374,832 60,802 5,160
/files/trash 1,100,767 373,611 60,868 3,939
/f/[id] 1,092,944 370,502 60,533 830
/invite/[token] 1,106,673 376,729 62,305 7,057
/journal 1,097,006 372,450 61,288 2,778
/login 1,097,623 372,804 61,206 3,132
/mail 1,123,702 380,528 62,682 10,856
/mail/c/[category] 1,124,154 380,829 62,682 11,157
/mail/f/[id] 1,123,813 380,592 62,682 10,920
/mail/m/[id] 1,123,814 380,593 62,682 10,921
/mail/t/[id] 1,123,813 380,592 62,682 10,920
/money 1,097,973 373,138 61,372 3,466
/money/[budget] 1,092,576 370,484 60,533 812
/money/[budget]/accounts/[[account]] 1,111,881 377,452 62,612 7,780
/money/[budget]/transactions/[transaction] 1,094,275 371,370 60,666 1,698
/money/[budget]/[month] 1,110,336 377,139 62,841 7,467
/notes 1,098,421 373,211 61,217 3,539
/notes/convert 1,099,614 372,853 61,269 3,181
/notes/[id] 1,867,122 612,891 67,284 243,219
/notifications 1,091,943 370,112 60,533 440
/n/[id] 1,091,904 370,052 60,656 380
/(photos)/photos/[...rest] 1,200,119 408,334 66,038 38,662
/(photos)/p/[id] 1,200,119 408,334 66,038 38,662
/recover 1,109,309 377,936 62,695 8,264
/reenrol 1,106,686 376,740 62,305 7,068
/search 1,092,109 370,191 60,533 519
/search/saved/[id] 1,092,828 370,569 60,533 897
/settings/[...path] 1,370,449 452,529 70,263 82,857
/setup 1,106,687 376,737 62,305 7,065
/shared 1,164,546 395,302 63,958 25,630
/signup 1,107,648 377,338 62,305 7,666
/s/[slug] 1,852,009 606,717 67,461 237,045
/tags 1,093,621 370,865 60,923 1,193
/tags/[tag] 1,091,675 369,969 60,533 297
/tag/[tag] 1,096,050 371,890 61,078 2,218
/today 1,091,753 369,989 60,533 317
/t/[id] 1,092,359 370,277 60,533 605
/[...path] 1,091,559 369,851 60,533 179

Largest chunks and why they load

Emitted file JS gzip Loading boundary
chunks/BYEvVsar.js 213,285 Editor/ProseMirror; required by Note route and public guest editor import
chunks/DdH_8ZmL.js 163,744 BklitAnalytics, React, motion and chart code; dynamic on a near-viewport chart
chunks/CvSg_AlF.js 155,087 hls.js; dynamic for HLS video
chunks/CvL5jGdk.js 150,468 Shared UI/viewer/calendar code and timezone city data; required by root layout
chunks/Covw4k9q.js 120,340 pdf.js; dynamic when PDF viewer mounts
nodes/0.Bk4XfXsR.js 82,845 Root layout, all Tab sidebars, Search, shortcuts and shared chrome
nodes/46.vEBmlLcs.js 76,896 All Settings sections
chunks/CF7-swT0.js 24,646 Generated action JSON; dynamic WebMCP registration
chunks/Df0kZKHp.js 23,369 Shared client runtime; initial
nodes/2.DW2whtud.js 22,955 Photos layout; required only for Photos routes
chunks/BEv57r6h.js 18,998 Composer; dynamic on open; its dependency closure is larger

The shared chunk has 423 latitude and 423 longitude fields. Source
apps/web/src/lib/appearance/autoScheme.svelte.ts:9 imports timezoneCity;
packages/ui/src/timezone-cities.ts:17 holds the full map. This map supports
Auto appearance, but importing it also loads it for the default appearance.
Use a lazy data boundary when Auto needs it. Preserve immediate theme paint.
Do not infer that this map alone accounts for the whole shared chunk.

The root layout imports Search and all sidebar implementations:
apps/web/src/routes/+layout.svelte:20–22 and
apps/web/src/lib/components/app-sidebar.svelte:51–66. Search imports the
Quick Look wrapper at lib/components/search-dialog.svelte:41.
Split optional surface implementations from synchronous action dispatch.
A hidden conditional component still contributes bytes through its static
import. Keep the shared UI exports and avoid copies of the same component.

Good existing boundaries:

  • Composer: dynamic import at root layout :71.
  • Search Note/Task preview: dynamic editor import after the selection settles,
    lib/search/SearchPreview.svelte:80–101.
  • Charts: lib/components/analytics/BklitChart.svelte:27,70–90 uses a dynamic
    React renderer and near-viewport observation. No chart renderer in the shell.
  • PDF: dynamic pdf.js at packages/ui/src/components/viewer/PdfView.svelte:33.
    Its worker is another 1,232,303 B raw / 361,770 B gzip .mjs asset,
    excluded from the initial route table. Total PDF first use must include it.
  • Text Quick Look: viewer/TextView.svelte:33–61 loads the core and selected
    grammar on demand. Editor grammars use a separate 12,241 B gzip chunk.
  • HLS remains optional. Native audio/video uses metadata preload. No map
    library is in the current web/UI dependency list; the planned Photos map
    is not an existing bundle finding.

Duplicated dependencies

The lockfile has one React 19.2.0, Yjs 13.6.33, TipTap core/pm 3.27.1 and
D3 array 3.2.1 resolution. These heavy packages do not have multiple locked
versions. Most duplicate versions are build/test tooling and must not be
counted as initial browser cost.

Highlight.js is a real reuse candidate: top-level 11.12.0, UI 11.11.1 and
lowlight's 11.11.2 (bun.lock:982,1516,1550). UI has a separate optional
core/grammar graph; the editor's lowlight and grammar loader use another
resolution. The manifest names the UI's 11.11.1 core; source names both
paths. This does not prove all three full packages ship or load initially.
Before deduplication, trace emitted modules and measure combined Editor +
Quick Look use. Use a compatible shared resolution or shared grammar loader,
then verify editor Markdown/schema behavior and highlighting. This evidence
was added to #497, rather than opening another broad bundle issue.

Fonts, images and cache policy

DESIGN §44's font choices are present. packages/ui/src/tokens.css:44–243
uses self-hosted Latin/Latin Extended WOFF2 subsets and font-display: swap.
Fallback metric faces follow at :248 onward. Fonts are selected by the boot
script at apps/web/src/app.html:291–314. System choices need no WOFF2.
There is no font preload in source or generated index. Swap permits fallback
text instead of an intentional font-load wait. No font-ready await gates the
route. This audit did not measure rendered font shifts.

Default Latin files: Google Sans 70,752 B; Bricolage Grotesque 76,888 B.
Maple Mono 67,420 B is needed only for code that uses it. All WOFF2 assets
total 1,685,316 B, including choices not used on first load. Do not preload
all fonts. First fix #804 caching. Then trace CSS discovery and optionally
preload only the selected, used UI/display Latin files with correct CORS/type.
Preloading defaults when a saved choice differs wastes bytes.

Photos and Search grids use loading="lazy" and decoding="async":
lib/photos/PhotoTimeline.svelte:656–657 and
lib/search/PhotoGrid.svelte:111. ImageView also decodes asynchronously.
Backgrounds resolve item identity and prefer a 1024-pixel thumbnail
(lib/appearance/background.svelte.ts:427–435); an item without a hash uses
its download URL. The boot mirror is bounded to a 128-pixel preview and
64 KiB of data URL text (:47–78). No image rewrite is proposed without a
trace of viewport images and selected background transfer. Keep original
bytes behind an explicit full view or download. Lazy images in the first
viewport still need decoded-image readiness checks.

Hashed _app/immutable/ assets have one-year public immutable caching.
index.html and other stable assets use no-cache. The asset helper has
no ETag or Last-Modified, so unchanged HTML, fonts, icons and worker cannot
get a conditional 304 there. HTML head rewriting makes a global immutable
HTML policy unsafe. #804 is the font-specific delivery owner.

The service worker is push-only (apps/web/src/service-worker.ts:5–15),
registered after notifications are enabled (svelte.config.js:15–18). It
has no fetch handler or app-shell cache. This is an explicit existing choice,
not a security finding. Keep HTTP caching as the first fix. A later shell
worker must cache public build assets only, retain a bounded prior build,
and preserve #423 update handling. Do not cache authenticated responses or
User HTML. User data still belongs to userStorage and its session-end
cleanup. No new worker is built here.

The binary embeds the SPA with RustEmbed (main.rs:72–74). On a release
build, cold assets can fault binary pages from disk; requests do not read
loose web files through this helper. Browser code size, parse cost and the
API/Index path are separate from this asset I/O. A slow-disk trace must name
which of these paths it delayed. No private edge headers were inspected.

Preload and first usable view

The document sets data-sveltekit-preload-data="hover" at app.html:319.
lib/navigation/modePreload.ts:55–70 follows redirects to a depth of three
and warms code/data together. It shares one in-flight promise per Tab and
clears readiness on session change. The root layout starts Tab warming after
pointerdown via a frame and timer (:479–488), and cold keyboard selection
starts it alongside navigation (:451–475). Selection does not await it.
This gives a short lead during a press; it does not warm all Tabs at startup.
There is no blanket initial preload of editor, chart, HLS or PDF code.

Recommended strategy: keep the critical shell small; warm one likely
next destination after first usable paint, on keyboard focus or pointer
intent. Cap concurrent work and retained code/data. Do not start every
Settings section or every Tab on idle. Respect reduced-data choices. Keep
current route and API data keys scoped to the User. Counts, folder discovery,
background images and optional views must not delay primary content.

Surface Main loading observation Existing follow-up
Calendar Resolver redirects to a concrete view; initial shell is large. #549 has HDD trace evidence, separate from this build. #497, #549, #669, #679
Notes List is small after shell; Note detail adds 243,219 B gzip. Revision/body caching is separate from schema load. #497, #665, #701, #704
Files Adds 24,423 B gzip; viewer implementations enter through shared Search. #497, #549, #670, #681
Photos Layout/view add 38,662 B gzip; thumbnails decode lazily. #497, #549, #671, #683
Mail Adds only 10,856 B gzip, but serial ancillary reads delay primary content. #640, #672, #686
Money Landing/month/register have small route deltas; body parsing/caching is a separate audit. #673, #689
Settings All sections load in one node; returning views improved on queued blaze-settings branch. #642, #556, #674, #692
Search Most code is already in the shell; settled Note preview brings in optional editor. #497, #675, #695
Admin Redirects into Settings; uses the same static section closure. Cached Role must not grant access. #642, #676, #698

Baseline and slow-disk limits

docs/perf/baseline.json is from 369ab6a2f, 2026-09-29. At 1440 px its
five-sample data-ready p50/p95 were: Files 1,240/1,442 ms, Notes list
1,257/1,337 ms, Photos 1,279/1,445 ms, Search 1,331/1,389 ms, Settings
1,584/2,069 ms and Today 1,589/1,830 ms. These are historical results, not
current-build timings or proof of 10k-item readiness. It recorded 347,695 B
gzip JS for Files and 400,869 B for Settings. Current static closures are
394,095 B and 452,529 B. Browser-observed assets and static closures differ,
so this is not a controlled before/after regression calculation.

docs/perf/2026-10-01-tabswitch-549.md records HDD qualification: 8 ms
read/write delay, 200 IOPS, direct-I/O loop and the server's capped cgroup.
It warns that earlier counters can finish on the source route and that
partial samples are not a baseline. Reuse its corrected DOM paint boundary.
The old partial measurements cannot prove this build's cold ready time.

No fresh ready-time, CPU or RSS claim is made here. Byte counts are exact
local build measurements and are not sensitive to host load. Before marking
any route compliant, run the existing route/tab/blaze harness on the perf VM
with the shared production release build, flock /root/perf.lock, load
recorded inside the lock, HDD emulation, at least five samples, and
median/p95/max. Define first usable as real primary content with input
accepted; do not use network idle, FCP alone or an animation's end. Include
10k items, a large Home and an input burst. Do not compile on the VM.

Proposed strict budgets — decisions for owner review

These are proposed byte allocations, not changes to DESIGN. Do not treat
misses as merge blockers. Use decimal bytes and the union of actually needed
files through first usable view, including runtime imports that block it.
Do not evade a budget by placing required work behind an immediate import.

Route group Initial JS gzip ceiling Initial CSS gzip ceiling Extra rule
Auth and public file/gallery links 100,000 25,000 No editor or authenticated sidebar code
Calendar, Notes list, Files, Photos, Mail, Money < 200,000 45,000 Shared shell ≤ 140,000; remaining route allocation ≤ 60,000
Settings and Admin < 200,000 45,000 Only selected section; same budget for every deep link
Search < 200,000 45,000 Optional previews load after primary results
Note/Task detail and public Note view < 200,000 45,000 Count any editor required for first usable content

Additional allocations: selected fonts ≤ 160,000 B before first usable
view; no code font on a code-free route. HTML gzip ≤ 15,000 B. Viewport
thumbnails plus background ≤ 500,000 B, each thumbnail ≤ 100,000 B.
Cache-hit immutable bytes should require zero network requests. Unchanged
stable fonts should need zero body bytes on revalidation. Optional chart,
editor, PDF and HLS must add zero bytes to routes that do not use them.
Do not preload the PDF worker or player on a file list.

Time ceilings remain #663: cached open ≤ 100 ms, accepted action ≤ 150 ms,
warm Tab switch ≤ 100 ms, first usable view ≤ 1.5 s at 10k items, warm blaze
0 incomplete frames. #642's more specific Settings cold ceiling is 400 ms.
Budgets need both a build graph report and browser readiness evidence.

Pending branch checks

Read-only comparisons used these local branch heads, not a new build of each:

Branch Head checked Effect on this audit
job/merge-round-7a 2f4482ded Settings adds Connected Accounts; source still imports sections statically. Core asset serving and lazy PDF remain unchanged.
job/instant-663 e62249ded Adds performance rules; §58 number collides with round 7a agent discovery.
job/blaze-settings bf3ad5f29 Keeps visited sections mounted and cached first paint; retains static section imports. Does not settle cold bundle cost.
job/fix-499 242022301 Palette layout/material changes; Search and Quick Look import roots remain.
job/perf-cache-665 b88bc6ac8 Revision/ETag primitive and Notes adoption; does not add static asset compression or font validators.
job/perf-snap-666 253c2a00c Snapshot library; does not change the bundle boundary.
job/writeonopen-661 04c4a651b Editor preset change; exact next-round editor size needs a rebuilt graph.

The round 7b list also includes mutation receipts, Journal projection writes,
Calendar images, voice playback and test-only probes. They can change future
bytes or readiness; do not copy this table's numbers to the next build.
All numerical sizes here belong to the origin/dev base, not round 7a/7b.

Validation and known gaps

Before final checks, git fetch origin && git merge origin/dev returned:

Already up to date.

Production build exited 0. Verbatim completion lines:

> Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done

Analyzer tests exited 0. Full verbatim output:

TAP version 13
# Subtest: shared files and CSS count once; dynamic editor stays outside initial closure
ok 1 - shared files and CSS count once; dynamic editor stays outside initial closure
  ---
  duration_ms: 17.107388
  type: 'test'
  ...
# Subtest: route parsing retains nested layouts and rejects an absent dictionary
ok 2 - route parsing retains nested layouts and rejects an absent dictionary
  ---
  duration_ms: 0.854689
  type: 'test'
  ...
# Subtest: route totals include nested layouts but leave optional chart outside shell
ok 3 - route totals include nested layouts but leave optional chart outside shell
  ---
  duration_ms: 5.534685
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 379.338456

git diff --check and both node --check commands exited 0 with no output. No Rust crate or app source
changed. Cargo fmt/clippy/test and web check/test suites were not run: the
job's read-mostly audit rule permits targeted proof checks and avoids full
shared-host gates. The production build and analyzer tests cover this change.
No API was changed, so no adversarial server campaign was run. The manifest
tests cover cycles, missing imports, shared CSS, nested layouts and exclusion
of optional chunks. This is not evidence of API security or UI correctness.

Known gaps: no fresh production-server encoding capture, private edge config,
HDD first-usable/CPU/RSS profile, rendered font-shift trace, or timed 400-page
PDF run. The source findings and follow-up tests state these limits. No
matching baseline exists for this analyzer's exact static-closure method.

UX gaps closed: none; audit only. UX gaps left: first-page PDF delay, cold
Settings loading and Mail ancillary-read gating are assigned to the issues
above. No screen or input-mode behavior was changed.

Decisions outside DESIGN: use a dependency-free manifest analyzer; retain
HTTP caching as the first proposed shell-cache fix; keep the worker push-only
in this audit; propose the per-route byte allocations above; count root error
code because Kit loads it eagerly. No product decision was implemented.

Completed bundle/loading audit on `job/perf-arch-bundle`. Head: `e8a263c3e9dda7b9a009b197a5255c17c66c98ba`. Built a dependency-free production manifest analyzer and three accounting tests. Files: `audit-findings.md`, `bench/bundle-audit.mjs`, `bench/bundle-audit.test.mjs`. No product edits, pushes or deployments. Fetch/merge of origin/dev was already up to date. New issues: #803 compression, #804 font caching, #805 PDF first-page loading. Evidence posted to existing #497, #642 and #672. No blocker confirmed. Production build and three targeted analyzer tests pass; syntax and whitespace checks pass. Rust/web source gates and adversarial API tests are not applicable to this audit-only change. Cargo clean and generated web-output cleanup were requested at completion. UX gaps closed: none (audit only). UX gaps left and decisions are in the report below. # Bundle and loading audit — #663 This report checks the production web build and its loading paths. It does not change the product. All size measurements below use the same source revision. A source check and a timed browser run are different evidence. ## Scope and method - Base: `origin/dev`, `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. - Branch: `job/perf-arch-bundle`. Audit date: 2026-10-02. - Read CLAUDE.md, CONTEXT.md and DESIGN before the audit. - DESIGN §58 is absent on the base. Round 7a uses §58 for agent discovery (#630). `job/instant-663` supplies the instant-interaction rules under the same number. These doc changes need reconciliation at merge. This audit uses #663, DESIGN §§18, 38, 44 and the performance text on `job/instant-663`. - Installed with `bun install --frozen-lockfile`; no dependency changes. Registry checks with `bun pm view <package>@<version> version` returned Vite `8.3.0`, SvelteKit `2.70.3` and Svelte `5.57.1`. - Ran one local production build with `bun run build` in `apps/web`. No Rust build, deployment or perf VM run was needed for byte counts. - `node bench/bundle-audit.mjs` reads the production Vite manifest and Kit's optimized route dictionary. It follows static imports only. It counts shared JS and CSS once per route. Gzip is applied to each file separately. - The shell includes entry/start, entry/app, node 0 and node 1. Kit `src/runtime/client/client.js:365–369` loads both root nodes eagerly. - Redirect rows describe only the resolver route. For `/today`, add the Calendar destination; for `/n/<id>` and `/t/<id>`, add the Note destination. Optional imports after mount, fonts, images and API responses are not in the static route totals. These totals do not prove time to first usable view. - `artifacts/build.log` and `artifacts/bundles.json` hold local raw evidence. The analyzer reproduces the counts after a build. No review images are committed. This audit makes no visual-quality claim. ## Findings and owners No merge-blocking security finding was confirmed in this audit. Performance findings do not block a merge. | Finding | Evidence and impact | Owner | | --- | --- | --- | | Required shell exceeds the existing initial-JS budget | 369,672 B gzip JS, before route-specific code; budget is less than 200,000 B. All routes inherit this cost. | Existing [#497](https://git.kayg.org/kayg/calternal/issues/497); evidence posted | | Settings loads every section | Settings adds 82,857 B gzip JS to the shell. Static imports at `apps/web/src/routes/settings/[...path]/+page.svelte:44–56` include Admin even for a User page. | Existing [#642](https://git.kayg.org/kayg/calternal/issues/642), [#556](https://git.kayg.org/kayg/calternal/issues/556); #642 evidence posted | | Public file links reach the guest editor | `apps/web/src/lib/files/PublicLinkPage.svelte:36` imports NoteEditorSurface; its use at :603 is conditional. A file gallery still has a 606,717 B gzip JS route closure. | #497; evidence posted; coordinate with DESIGN §54 view-only links | | Direct static delivery has no compression | `crates/calternal-server/src/main.rs:1074–1096,1131–1160` sends raw embedded bytes. No compression layer or encoding negotiation exists. Shell JS/CSS total 1,432,913 B raw versus 430,205 B gzip. Private Traefik configuration was not inspected. | New [#803](https://git.kayg.org/kayg/calternal/issues/803) | | Stable font URLs cannot get a conditional 304 | `/fonts/*.woff2` gets `no-cache`, without ETag or Last-Modified, from the same asset path. Default Latin UI/display faces total 147,640 B. Repeated document loads need fresh font transfers from this server. | New [#804](https://git.kayg.org/kayg/calternal/issues/804) | | PDF first page waits for all page sizes | `packages/ui/src/components/viewer/PdfView.svelte:46–52` awaits getPage for every page before publishing any canvases. Work before first page grows with total pages. This is reasoned impact, not measured latency. | New [#805](https://git.kayg.org/kayg/calternal/issues/805); coordinate with #741 text accessibility | | Mail primary content waits for ancillary reads | `apps/web/src/lib/mail/MailView.svelte:324–380` awaits accounts, folders and preferences before list/detail. A thread also awaits attachment metadata before its first body. | Existing [#672](https://git.kayg.org/kayg/calternal/issues/672), #640; #672 evidence posted | Duplicate searches used open and all issue searches for bundle, compression, gzip, WOFF2, font cache and PDF. The three new issues each include source revision, file:line, impact, a concrete fix and a test. Existing #497, #642, #556, #640 and #672 keep their scope. #234/#322 own font choices; #547 owns server PDF thumbnails; #741 owns PDF accessibility. They do not cover the new delivery or first-page findings. ## Production route sizes All numbers are bytes. JS raw is emitted, minified code before compression. JS delta is the static closure added to the shared shell. A zero-data route can still run further imports when it mounts. Query parameters do not create another code split. The catch-all Settings row covers User and Admin pages. Shared shell: **1,091,341 B raw JS / 369,672 B gzip JS**, 77 JS files. CSS: **341,572 B raw / 60,533 B gzip**, 5 CSS files. | Kit route | JS raw | JS gzip | CSS gzip | JS delta | | --- | ---: | ---: | ---: | ---: | | `/` | 1,091,820 | 370,044 | 60,533 | 372 | | `/admin/[...page]` | 1,091,747 | 370,011 | 60,533 | 339 | | `/ai/turns/[id]` | 1,091,986 | 370,082 | 60,533 | 410 | | `/analytics` | 1,091,804 | 370,028 | 60,533 | 356 | | `/analytics/[period]` | 1,091,841 | 370,049 | 60,533 | 377 | | `/analytics/[period]/[date]` | 1,099,919 | 373,130 | 60,837 | 3,458 | | `/ask` | 1,100,371 | 373,630 | 61,717 | 3,958 | | `/calendar` | 1,091,753 | 369,989 | 60,533 | 317 | | `/calendar/[view]` | 1,091,790 | 370,029 | 60,533 | 357 | | `/calendar/[view]/[date]` | 1,148,205 | 389,945 | 61,409 | 20,273 | | `/d/[date]` | 1,092,448 | 370,339 | 60,658 | 667 | | `/e/[id]` | 1,092,458 | 370,310 | 60,533 | 638 | | `/files` | 1,161,750 | 394,095 | 63,403 | 24,423 | | `/files/recent` | 1,103,912 | 374,832 | 60,802 | 5,160 | | `/files/trash` | 1,100,767 | 373,611 | 60,868 | 3,939 | | `/f/[id]` | 1,092,944 | 370,502 | 60,533 | 830 | | `/invite/[token]` | 1,106,673 | 376,729 | 62,305 | 7,057 | | `/journal` | 1,097,006 | 372,450 | 61,288 | 2,778 | | `/login` | 1,097,623 | 372,804 | 61,206 | 3,132 | | `/mail` | 1,123,702 | 380,528 | 62,682 | 10,856 | | `/mail/c/[category]` | 1,124,154 | 380,829 | 62,682 | 11,157 | | `/mail/f/[id]` | 1,123,813 | 380,592 | 62,682 | 10,920 | | `/mail/m/[id]` | 1,123,814 | 380,593 | 62,682 | 10,921 | | `/mail/t/[id]` | 1,123,813 | 380,592 | 62,682 | 10,920 | | `/money` | 1,097,973 | 373,138 | 61,372 | 3,466 | | `/money/[budget]` | 1,092,576 | 370,484 | 60,533 | 812 | | `/money/[budget]/accounts/[[account]]` | 1,111,881 | 377,452 | 62,612 | 7,780 | | `/money/[budget]/transactions/[transaction]` | 1,094,275 | 371,370 | 60,666 | 1,698 | | `/money/[budget]/[month]` | 1,110,336 | 377,139 | 62,841 | 7,467 | | `/notes` | 1,098,421 | 373,211 | 61,217 | 3,539 | | `/notes/convert` | 1,099,614 | 372,853 | 61,269 | 3,181 | | `/notes/[id]` | 1,867,122 | 612,891 | 67,284 | 243,219 | | `/notifications` | 1,091,943 | 370,112 | 60,533 | 440 | | `/n/[id]` | 1,091,904 | 370,052 | 60,656 | 380 | | `/(photos)/photos/[...rest]` | 1,200,119 | 408,334 | 66,038 | 38,662 | | `/(photos)/p/[id]` | 1,200,119 | 408,334 | 66,038 | 38,662 | | `/recover` | 1,109,309 | 377,936 | 62,695 | 8,264 | | `/reenrol` | 1,106,686 | 376,740 | 62,305 | 7,068 | | `/search` | 1,092,109 | 370,191 | 60,533 | 519 | | `/search/saved/[id]` | 1,092,828 | 370,569 | 60,533 | 897 | | `/settings/[...path]` | 1,370,449 | 452,529 | 70,263 | 82,857 | | `/setup` | 1,106,687 | 376,737 | 62,305 | 7,065 | | `/shared` | 1,164,546 | 395,302 | 63,958 | 25,630 | | `/signup` | 1,107,648 | 377,338 | 62,305 | 7,666 | | `/s/[slug]` | 1,852,009 | 606,717 | 67,461 | 237,045 | | `/tags` | 1,093,621 | 370,865 | 60,923 | 1,193 | | `/tags/[tag]` | 1,091,675 | 369,969 | 60,533 | 297 | | `/tag/[tag]` | 1,096,050 | 371,890 | 61,078 | 2,218 | | `/today` | 1,091,753 | 369,989 | 60,533 | 317 | | `/t/[id]` | 1,092,359 | 370,277 | 60,533 | 605 | | `/[...path]` | 1,091,559 | 369,851 | 60,533 | 179 | ## Largest chunks and why they load | Emitted file | JS gzip | Loading boundary | | --- | ---: | --- | | `chunks/BYEvVsar.js` | 213,285 | Editor/ProseMirror; required by Note route and public guest editor import | | `chunks/DdH_8ZmL.js` | 163,744 | BklitAnalytics, React, motion and chart code; dynamic on a near-viewport chart | | `chunks/CvSg_AlF.js` | 155,087 | hls.js; dynamic for HLS video | | `chunks/CvL5jGdk.js` | 150,468 | Shared UI/viewer/calendar code and timezone city data; required by root layout | | `chunks/Covw4k9q.js` | 120,340 | pdf.js; dynamic when PDF viewer mounts | | `nodes/0.Bk4XfXsR.js` | 82,845 | Root layout, all Tab sidebars, Search, shortcuts and shared chrome | | `nodes/46.vEBmlLcs.js` | 76,896 | All Settings sections | | `chunks/CF7-swT0.js` | 24,646 | Generated action JSON; dynamic WebMCP registration | | `chunks/Df0kZKHp.js` | 23,369 | Shared client runtime; initial | | `nodes/2.DW2whtud.js` | 22,955 | Photos layout; required only for Photos routes | | `chunks/BEv57r6h.js` | 18,998 | Composer; dynamic on open; its dependency closure is larger | The shared chunk has 423 `latitude` and 423 `longitude` fields. Source `apps/web/src/lib/appearance/autoScheme.svelte.ts:9` imports `timezoneCity`; `packages/ui/src/timezone-cities.ts:17` holds the full map. This map supports Auto appearance, but importing it also loads it for the default appearance. Use a lazy data boundary when Auto needs it. Preserve immediate theme paint. Do not infer that this map alone accounts for the whole shared chunk. The root layout imports Search and all sidebar implementations: `apps/web/src/routes/+layout.svelte:20–22` and `apps/web/src/lib/components/app-sidebar.svelte:51–66`. Search imports the Quick Look wrapper at `lib/components/search-dialog.svelte:41`. Split optional surface implementations from synchronous action dispatch. A hidden conditional component still contributes bytes through its static import. Keep the shared UI exports and avoid copies of the same component. Good existing boundaries: - Composer: dynamic import at root layout :71. - Search Note/Task preview: dynamic editor import after the selection settles, `lib/search/SearchPreview.svelte:80–101`. - Charts: `lib/components/analytics/BklitChart.svelte:27,70–90` uses a dynamic React renderer and near-viewport observation. No chart renderer in the shell. - PDF: dynamic pdf.js at `packages/ui/src/components/viewer/PdfView.svelte:33`. Its worker is another **1,232,303 B raw / 361,770 B gzip** `.mjs` asset, excluded from the initial route table. Total PDF first use must include it. - Text Quick Look: `viewer/TextView.svelte:33–61` loads the core and selected grammar on demand. Editor grammars use a separate 12,241 B gzip chunk. - HLS remains optional. Native audio/video uses metadata preload. No map library is in the current web/UI dependency list; the planned Photos map is not an existing bundle finding. ## Duplicated dependencies The lockfile has one React 19.2.0, Yjs 13.6.33, TipTap core/pm 3.27.1 and D3 array 3.2.1 resolution. These heavy packages do not have multiple locked versions. Most duplicate versions are build/test tooling and must not be counted as initial browser cost. Highlight.js is a real reuse candidate: top-level 11.12.0, UI 11.11.1 and lowlight's 11.11.2 (`bun.lock:982,1516,1550`). UI has a separate optional core/grammar graph; the editor's lowlight and grammar loader use another resolution. The manifest names the UI's 11.11.1 core; source names both paths. This does not prove all three full packages ship or load initially. Before deduplication, trace emitted modules and measure combined Editor + Quick Look use. Use a compatible shared resolution or shared grammar loader, then verify editor Markdown/schema behavior and highlighting. This evidence was added to #497, rather than opening another broad bundle issue. ## Fonts, images and cache policy DESIGN §44's font choices are present. `packages/ui/src/tokens.css:44–243` uses self-hosted Latin/Latin Extended WOFF2 subsets and `font-display: swap`. Fallback metric faces follow at :248 onward. Fonts are selected by the boot script at `apps/web/src/app.html:291–314`. System choices need no WOFF2. There is no font preload in source or generated index. Swap permits fallback text instead of an intentional font-load wait. No font-ready await gates the route. This audit did not measure rendered font shifts. Default Latin files: Google Sans 70,752 B; Bricolage Grotesque 76,888 B. Maple Mono 67,420 B is needed only for code that uses it. All WOFF2 assets total 1,685,316 B, including choices not used on first load. Do not preload all fonts. First fix #804 caching. Then trace CSS discovery and optionally preload only the selected, used UI/display Latin files with correct CORS/type. Preloading defaults when a saved choice differs wastes bytes. Photos and Search grids use `loading="lazy"` and `decoding="async"`: `lib/photos/PhotoTimeline.svelte:656–657` and `lib/search/PhotoGrid.svelte:111`. ImageView also decodes asynchronously. Backgrounds resolve item identity and prefer a 1024-pixel thumbnail (`lib/appearance/background.svelte.ts:427–435`); an item without a hash uses its download URL. The boot mirror is bounded to a 128-pixel preview and 64 KiB of data URL text (:47–78). No image rewrite is proposed without a trace of viewport images and selected background transfer. Keep original bytes behind an explicit full view or download. Lazy images in the first viewport still need decoded-image readiness checks. Hashed `_app/immutable/` assets have one-year public immutable caching. `index.html` and other stable assets use `no-cache`. The asset helper has no ETag or Last-Modified, so unchanged HTML, fonts, icons and worker cannot get a conditional 304 there. HTML head rewriting makes a global immutable HTML policy unsafe. #804 is the font-specific delivery owner. The service worker is push-only (`apps/web/src/service-worker.ts:5–15`), registered after notifications are enabled (`svelte.config.js:15–18`). It has no fetch handler or app-shell cache. This is an explicit existing choice, not a security finding. Keep HTTP caching as the first fix. A later shell worker must cache public build assets only, retain a bounded prior build, and preserve #423 update handling. Do not cache authenticated responses or User HTML. User data still belongs to `userStorage` and its session-end cleanup. No new worker is built here. The binary embeds the SPA with RustEmbed (`main.rs:72–74`). On a release build, cold assets can fault binary pages from disk; requests do not read loose web files through this helper. Browser code size, parse cost and the API/Index path are separate from this asset I/O. A slow-disk trace must name which of these paths it delayed. No private edge headers were inspected. ## Preload and first usable view The document sets `data-sveltekit-preload-data="hover"` at app.html:319. `lib/navigation/modePreload.ts:55–70` follows redirects to a depth of three and warms code/data together. It shares one in-flight promise per Tab and clears readiness on session change. The root layout starts Tab warming after pointerdown via a frame and timer (:479–488), and cold keyboard selection starts it alongside navigation (:451–475). Selection does not await it. This gives a short lead during a press; it does not warm all Tabs at startup. There is no blanket initial preload of editor, chart, HLS or PDF code. Recommended strategy: keep the critical shell small; warm one likely next destination after first usable paint, on keyboard focus or pointer intent. Cap concurrent work and retained code/data. Do not start every Settings section or every Tab on idle. Respect reduced-data choices. Keep current route and API data keys scoped to the User. Counts, folder discovery, background images and optional views must not delay primary content. | Surface | Main loading observation | Existing follow-up | | --- | --- | --- | | Calendar | Resolver redirects to a concrete view; initial shell is large. #549 has HDD trace evidence, separate from this build. | #497, #549, #669, #679 | | Notes | List is small after shell; Note detail adds 243,219 B gzip. Revision/body caching is separate from schema load. | #497, #665, #701, #704 | | Files | Adds 24,423 B gzip; viewer implementations enter through shared Search. | #497, #549, #670, #681 | | Photos | Layout/view add 38,662 B gzip; thumbnails decode lazily. | #497, #549, #671, #683 | | Mail | Adds only 10,856 B gzip, but serial ancillary reads delay primary content. | #640, #672, #686 | | Money | Landing/month/register have small route deltas; body parsing/caching is a separate audit. | #673, #689 | | Settings | All sections load in one node; returning views improved on queued blaze-settings branch. | #642, #556, #674, #692 | | Search | Most code is already in the shell; settled Note preview brings in optional editor. | #497, #675, #695 | | Admin | Redirects into Settings; uses the same static section closure. Cached Role must not grant access. | #642, #676, #698 | ## Baseline and slow-disk limits `docs/perf/baseline.json` is from `369ab6a2f`, 2026-09-29. At 1440 px its five-sample data-ready p50/p95 were: Files 1,240/1,442 ms, Notes list 1,257/1,337 ms, Photos 1,279/1,445 ms, Search 1,331/1,389 ms, Settings 1,584/2,069 ms and Today 1,589/1,830 ms. These are historical results, not current-build timings or proof of 10k-item readiness. It recorded 347,695 B gzip JS for Files and 400,869 B for Settings. Current static closures are 394,095 B and 452,529 B. Browser-observed assets and static closures differ, so this is not a controlled before/after regression calculation. `docs/perf/2026-10-01-tabswitch-549.md` records HDD qualification: 8 ms read/write delay, 200 IOPS, direct-I/O loop and the server's capped cgroup. It warns that earlier counters can finish on the source route and that partial samples are not a baseline. Reuse its corrected DOM paint boundary. The old partial measurements cannot prove this build's cold ready time. No fresh ready-time, CPU or RSS claim is made here. Byte counts are exact local build measurements and are not sensitive to host load. Before marking any route compliant, run the existing route/tab/blaze harness on the perf VM with the shared production release build, `flock /root/perf.lock`, load recorded inside the lock, HDD emulation, at least five samples, and median/p95/max. Define first usable as real primary content with input accepted; do not use network idle, FCP alone or an animation's end. Include 10k items, a large Home and an input burst. Do not compile on the VM. ## Proposed strict budgets — decisions for owner review These are proposed byte allocations, not changes to DESIGN. Do not treat misses as merge blockers. Use decimal bytes and the union of actually needed files through first usable view, including runtime imports that block it. Do not evade a budget by placing required work behind an immediate import. | Route group | Initial JS gzip ceiling | Initial CSS gzip ceiling | Extra rule | | --- | ---: | ---: | --- | | Auth and public file/gallery links | 100,000 | 25,000 | No editor or authenticated sidebar code | | Calendar, Notes list, Files, Photos, Mail, Money | < 200,000 | 45,000 | Shared shell ≤ 140,000; remaining route allocation ≤ 60,000 | | Settings and Admin | < 200,000 | 45,000 | Only selected section; same budget for every deep link | | Search | < 200,000 | 45,000 | Optional previews load after primary results | | Note/Task detail and public Note view | < 200,000 | 45,000 | Count any editor required for first usable content | Additional allocations: selected fonts ≤ 160,000 B before first usable view; no code font on a code-free route. HTML gzip ≤ 15,000 B. Viewport thumbnails plus background ≤ 500,000 B, each thumbnail ≤ 100,000 B. Cache-hit immutable bytes should require zero network requests. Unchanged stable fonts should need zero body bytes on revalidation. Optional chart, editor, PDF and HLS must add **zero** bytes to routes that do not use them. Do not preload the PDF worker or player on a file list. Time ceilings remain #663: cached open ≤ 100 ms, accepted action ≤ 150 ms, warm Tab switch ≤ 100 ms, first usable view ≤ 1.5 s at 10k items, warm blaze 0 incomplete frames. #642's more specific Settings cold ceiling is 400 ms. Budgets need both a build graph report and browser readiness evidence. ## Pending branch checks Read-only comparisons used these local branch heads, not a new build of each: | Branch | Head checked | Effect on this audit | | --- | --- | --- | | job/merge-round-7a | 2f4482ded | Settings adds Connected Accounts; source still imports sections statically. Core asset serving and lazy PDF remain unchanged. | | job/instant-663 | e62249ded | Adds performance rules; §58 number collides with round 7a agent discovery. | | job/blaze-settings | bf3ad5f29 | Keeps visited sections mounted and cached first paint; retains static section imports. Does not settle cold bundle cost. | | job/fix-499 | 242022301 | Palette layout/material changes; Search and Quick Look import roots remain. | | job/perf-cache-665 | b88bc6ac8 | Revision/ETag primitive and Notes adoption; does not add static asset compression or font validators. | | job/perf-snap-666 | 253c2a00c | Snapshot library; does not change the bundle boundary. | | job/writeonopen-661 | 04c4a651b | Editor preset change; exact next-round editor size needs a rebuilt graph. | The round 7b list also includes mutation receipts, Journal projection writes, Calendar images, voice playback and test-only probes. They can change future bytes or readiness; do not copy this table's numbers to the next build. All numerical sizes here belong to the origin/dev base, not round 7a/7b. ## Validation and known gaps Before final checks, `git fetch origin && git merge origin/dev` returned: ```text Already up to date. ``` Production build exited 0. Verbatim completion lines: ```text > Using @sveltejs/adapter-static Wrote site to "build" ✔ done ``` Analyzer tests exited 0. Full verbatim output: ```text TAP version 13 # Subtest: shared files and CSS count once; dynamic editor stays outside initial closure ok 1 - shared files and CSS count once; dynamic editor stays outside initial closure --- duration_ms: 17.107388 type: 'test' ... # Subtest: route parsing retains nested layouts and rejects an absent dictionary ok 2 - route parsing retains nested layouts and rejects an absent dictionary --- duration_ms: 0.854689 type: 'test' ... # Subtest: route totals include nested layouts but leave optional chart outside shell ok 3 - route totals include nested layouts but leave optional chart outside shell --- duration_ms: 5.534685 type: 'test' ... 1..3 # tests 3 # suites 0 # pass 3 # fail 0 # cancelled 0 # skipped 0 # todo 0 # duration_ms 379.338456 ``` `git diff --check` and both `node --check` commands exited 0 with no output. No Rust crate or app source changed. Cargo fmt/clippy/test and web check/test suites were not run: the job's read-mostly audit rule permits targeted proof checks and avoids full shared-host gates. The production build and analyzer tests cover this change. No API was changed, so no adversarial server campaign was run. The manifest tests cover cycles, missing imports, shared CSS, nested layouts and exclusion of optional chunks. This is not evidence of API security or UI correctness. Known gaps: no fresh production-server encoding capture, private edge config, HDD first-usable/CPU/RSS profile, rendered font-shift trace, or timed 400-page PDF run. The source findings and follow-up tests state these limits. No matching baseline exists for this analyzer's exact static-closure method. UX gaps closed: none; audit only. UX gaps left: first-page PDF delay, cold Settings loading and Mail ancillary-read gating are assigned to the issues above. No screen or input-mode behavior was changed. Decisions outside DESIGN: use a dependency-free manifest analyzer; retain HTTP caching as the first proposed shell-cache fix; keep the worker push-only in this audit; propose the per-route byte allocations above; count root error code because Kit loads it eagerly. No product decision was implemented.
Author
Owner

Memory and CPU architecture audit complete

Branch: job/perf-arch-memory
Base/final origin/dev: c4a61e8cf0
Head: 1ea39f4007
Atomic commits: a42ad505d, 3a86aac14, 1ea39f400.
File: audit-findings.md (the only committed change). No product edits.

Built a source-backed memory/CPU report with resident-state and queue inventory, per-User and per-connection costs, idle timer inventory, estimates for 1k Users / 100k files / 100k mails, pending-branch checks and concrete fixes/regression tests.

Filed after all-state duplicate searches:

  • #761: unbounded collaboration filesystem event queue.
  • #762: Unsplash thumbnail bytes and idle expiry; 512 maximum-size thumbnails can retain 2,560 MiB, plus response copies.
  • #763: Mail IDLE/polling waits occupy the three global sync slots; with 1,000 quiet accounts, a rotation needs at least 27.8 h of slot time, excluding provider work.
  • #764: empty Worker performs an UPDATE lease attempt each second; coordinate the core cron deadline loop.
  • #807: Money parsed-file cache lacks a byte budget and deep-clones hits; 64 warm Users × 100 files × 100 KiB retain at least 625 MiB of raw text.
  • #808: Notes IMAP holds mailbox source text per connection; 64 connections at source/base limits retain up to 1,536 MiB, with another 1,024 MiB during overlapping refreshes. More than 4,096 Notes fails before Tag filtering.
  • #809: Notes and saved-search strong lock registries retain historical User IDs; low priority, estimated 0.2–0.4 MiB per 1k historical Users.

All numerical impacts above are reasoned allocation/work estimates, not new VM RSS or latency measurements. The report includes the historical baseline separately: idle RSS 176.6 MiB, idle CPU 0.2%; 100k Search full-reindex peak 1,133.1 MiB. These runs do not establish a current loaded RSS guarantee.

Added evidence to #668 for Instance-wide Files SSE signal fan-out (up to one query per open stream on another User's change) and #695 for periodic full-text reconciliation. Reused these issues rather than file duplicates.

Checked round 7a at 2f4482ded and the round 7b queue/refs. Excluded the old Search full-manifest defect (#496/#503; fixed in 7a) and old MCP Events 10 Hz loop (#491; event-driven in 7a). The seven new findings remain in audited pending code.

Validation (verbatim output)

Before final gates, fetched origin and merged origin/dev once:

Already up to date.
cargo fmt --check exit: 0
git diff --check exit: 0
Audit evidence check: 34 source assertions passed; 7 baseline/arithmetic assertions passed; 7 filed issue references passed.
Web build output: absent; no cleanup needed
     Removed 1 file, 356B total
cargo clean exit: 0

The fmt/diff commands produced no diagnostics; exit lines are wrapper output. Source checks validate facts/arithmetic, not runtime behavior. No Rust crate or web package changed, so crate Clippy/tests and web gates do not apply. No API/behavior change was merged, so an API adversarial round does not apply. Small-build cargo environment used; preset CARGO_TARGET_DIR unchanged. Worktree clean; no push or deployment.

Known gaps and decisions

Fixes and proposed regression tests remain in the seven issues. Fresh total RSS/CPU measurements remain for the periodic run after pending code deploys. Models exclude ONNX/Tantivy resident pages, allocator fragmentation and kernel socket memory. The full Tab × rules matrix belongs to the other #663 audit jobs.

Decision: use source/allocation models where they prove a capacity defect, without a server build or VM measurement. Do not label performance risks BLOCKER without evidence of a merge-blocking failure. No crash, data-loss or authorization failure was reproduced here.

Decision: use #663 and job/instant-663's performance §58 as policy. Dev has no §58; round 7a uses §58 for agent setup. Reconcile numbering at merge.

UX gaps closed/left: not applicable; no UI changes. No issue closed.

## Memory and CPU architecture audit complete Branch: job/perf-arch-memory Base/final origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 Head: 1ea39f4007d227fea90ad0c7b8a81a297bd9352e Atomic commits: a42ad505d, 3a86aac14, 1ea39f400. File: audit-findings.md (the only committed change). No product edits. Built a source-backed memory/CPU report with resident-state and queue inventory, per-User and per-connection costs, idle timer inventory, estimates for 1k Users / 100k files / 100k mails, pending-branch checks and concrete fixes/regression tests. Filed after all-state duplicate searches: - #761: unbounded collaboration filesystem event queue. - #762: Unsplash thumbnail bytes and idle expiry; 512 maximum-size thumbnails can retain 2,560 MiB, plus response copies. - #763: Mail IDLE/polling waits occupy the three global sync slots; with 1,000 quiet accounts, a rotation needs at least 27.8 h of slot time, excluding provider work. - #764: empty Worker performs an UPDATE lease attempt each second; coordinate the core cron deadline loop. - #807: Money parsed-file cache lacks a byte budget and deep-clones hits; 64 warm Users × 100 files × 100 KiB retain at least 625 MiB of raw text. - #808: Notes IMAP holds mailbox source text per connection; 64 connections at source/base limits retain up to 1,536 MiB, with another 1,024 MiB during overlapping refreshes. More than 4,096 Notes fails before Tag filtering. - #809: Notes and saved-search strong lock registries retain historical User IDs; low priority, estimated 0.2–0.4 MiB per 1k historical Users. All numerical impacts above are reasoned allocation/work estimates, not new VM RSS or latency measurements. The report includes the historical baseline separately: idle RSS 176.6 MiB, idle CPU 0.2%; 100k Search full-reindex peak 1,133.1 MiB. These runs do not establish a current loaded RSS guarantee. Added evidence to #668 for Instance-wide Files SSE signal fan-out (up to one query per open stream on another User's change) and #695 for periodic full-text reconciliation. Reused these issues rather than file duplicates. Checked round 7a at 2f4482ded and the round 7b queue/refs. Excluded the old Search full-manifest defect (#496/#503; fixed in 7a) and old MCP Events 10 Hz loop (#491; event-driven in 7a). The seven new findings remain in audited pending code. ## Validation (verbatim output) Before final gates, fetched origin and merged origin/dev once: ```text Already up to date. ``` ```text cargo fmt --check exit: 0 git diff --check exit: 0 Audit evidence check: 34 source assertions passed; 7 baseline/arithmetic assertions passed; 7 filed issue references passed. Web build output: absent; no cleanup needed Removed 1 file, 356B total cargo clean exit: 0 ``` The fmt/diff commands produced no diagnostics; exit lines are wrapper output. Source checks validate facts/arithmetic, not runtime behavior. No Rust crate or web package changed, so crate Clippy/tests and web gates do not apply. No API/behavior change was merged, so an API adversarial round does not apply. Small-build cargo environment used; preset CARGO_TARGET_DIR unchanged. Worktree clean; no push or deployment. ## Known gaps and decisions Fixes and proposed regression tests remain in the seven issues. Fresh total RSS/CPU measurements remain for the periodic run after pending code deploys. Models exclude ONNX/Tantivy resident pages, allocator fragmentation and kernel socket memory. The full Tab × rules matrix belongs to the other #663 audit jobs. Decision: use source/allocation models where they prove a capacity defect, without a server build or VM measurement. Do not label performance risks BLOCKER without evidence of a merge-blocking failure. No crash, data-loss or authorization failure was reproduced here. Decision: use #663 and job/instant-663's performance §58 as policy. Dev has no §58; round 7a uses §58 for agent setup. Reconcile numbering at merge. UX gaps closed/left: not applicable; no UI changes. No issue closed.
Author
Owner

Completed the assigned defensive protocol source audit.

Branch: job/sec-protocols
Base: c4a61e8cf0
Head: dc244e8b60
Commits: d5b1f5af4 (findings), a07a92037 (repair links/source pins), dc244e8b6 (verification).
Files: audit-findings.md only. No product edits, dependency changes, migrations, pushes or deployment. Final fetch/merge of origin/dev: Already up to date.

Built: protocol security inventory with file:line evidence, reasoned impact, repair requirements and regression coverage. Five separate BLOCKER repair issues after duplicate searches:

  • #785 DAV XML lacks an element-depth bound.
  • #786 Notes IMAP omits configured IP/User connection permits.
  • #787 active Notes sessions do not enforce App Password expiry.
  • #788 Files/Notes SSE can emit paths after authorization ends.
  • #789 legacy MCP session stream/control access has no authenticated-owner binding (conditional on obtaining the session ID).

Known gaps: all five fixes remain for their repair jobs. Findings are source-based; no crash threshold or live exploitation measured. No real-server adversarial or production-proxy framing run. Committed #486 is not yet mounted as Mail in the listener; re-audit the finished Mail proxy. No inbound CardDAV server found in audited source. Initialization/session quotas and DAV body-duration bounds need follow-up validation. Existing tests validate protocol controls, not repair of P1–P5.

Decisions: followed the explicit protocol audit scope despite #663 being a performance issue and §58 being a queued performance section; kept product edits out of this read-mostly job; marked concrete authorization gaps and input-dependent XML stack exhaustion risks as blockers without claiming a reproduced crash. No new user-facing feature, route or background job was added, so no benchmark profile or UI screenshots apply.

Verification: cargo fmt --check and git diff --check exited 0 with no output. Scratch SQLite contract check passed for the expiry predicates using synthetic rows. cargo test -p calternal-imap --locked exited 0: 48 tests passed. No Rust/web crate changed, so no crate clippy, server-route or web gates were added. Cargo environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR in this worktree; preset CARGO_TARGET_DIR preserved.

Verbatim cargo test output:

   Compiling proc-macro2 v1.0.107
   Compiling quote v1.0.47
   Compiling unicode-ident v1.0.26
   Compiling cfg-if v1.0.5
   Compiling libc v0.2.189
   Compiling zerocopy v0.8.58
   Compiling siphasher v1.0.3
   Compiling getrandom v0.2.17
   Compiling siphasher v0.3.11
   Compiling ppv-lite86 v0.2.21
   Compiling rand_core v0.6.4
   Compiling phf_shared v0.11.3
   Compiling phf_shared v0.10.0
   Compiling rand_chacha v0.3.1
   Compiling autocfg v1.5.1
   Compiling serde_core v1.0.229
   Compiling syn v3.0.6
   Compiling syn v2.0.119
   Compiling rand v0.8.8
   Compiling num-traits v0.2.19
   Compiling getrandom v0.3.4
   Compiling serde v1.0.229
   Compiling smallvec v1.16.1
   Compiling phf_generator v0.11.3
   Compiling phf_generator v0.10.0
   Compiling bitflags v2.13.2
   Compiling once_cell v1.21.4
   Compiling phf_codegen v0.10.0
   Compiling serde_derive v1.0.229
   Compiling new_debug_unreachable v1.0.6
   Compiling parking_lot_core v0.9.12
   Compiling shlex v2.0.1
   Compiling itoa v1.0.18
   Compiling memchr v2.8.3
   Compiling scopeguard v1.2.0
   Compiling rustix v1.1.5
   Compiling find-msvc-tools v0.1.13
   Compiling syn v1.0.109
   Compiling iana-time-zone v0.1.65
   Compiling lock_api v0.4.14
   Compiling cc v1.4.7
   Compiling chrono v0.4.45
   Compiling string_cache_codegen v0.5.4
   Compiling log v0.4.34
   Compiling hashbrown v0.17.1
   Compiling thiserror v2.0.21
   Compiling linux-raw-sys v0.12.1
   Compiling equivalent v1.0.2
   Compiling precomputed-hash v0.1.1
   Compiling mac v0.1.1
   Compiling indexmap v2.14.2
   Compiling futf v0.1.5
   Compiling phf v0.10.1
   Compiling markup5ever v0.11.0
   Compiling blake3 v1.8.7
   Compiling parking_lot v0.12.5
   Compiling phf_macros v0.11.3
   Compiling thiserror-impl v2.0.21
   Compiling dtoa v1.0.11
   Compiling zmij v1.0.23
   Compiling utf-8 v0.7.6
   Compiling getrandom v0.4.3
   Compiling version_check v0.9.5
   Compiling dtoa-short v0.3.5
   Compiling tendril v0.4.3
   Compiling html5ever v0.26.0
   Compiling string_cache v0.8.9
   Compiling ahash v0.8.12
   Compiling phf v0.11.3
   Compiling rand_core v0.9.5
   Compiling selectors v0.25.0
   Compiling cssparser-macros v0.6.1
   Compiling phf_shared v0.12.1
   Compiling errno v0.3.14
   Compiling unicode-joining-type v1.0.0
   Compiling tinyvec v1.13.3
   Compiling serde_json v1.0.151
   Compiling minimal-lexical v0.2.1
   Compiling stable_deref_trait v1.2.1
   Compiling constant_time_eq v0.4.2
   Compiling cpufeatures v0.3.1
   Compiling chrono-tz v0.10.4
   Compiling pin-project-lite v0.2.17
   Compiling arrayvec v0.7.8
   Compiling byteorder v1.5.0
   Compiling servo_arc v0.3.0
   Compiling nom v7.1.3
   Compiling fxhash v0.2.1
   Compiling unicode-normalization v0.1.25
   Compiling signal-hook-registry v1.4.8
   Compiling phf v0.12.1
   Compiling cssparser v0.31.2
   Compiling tracing-core v0.1.36
   Compiling derive_more v0.99.20
   Compiling tracing-attributes v0.1.31
   Compiling bounded-static-derive v0.8.0
   Compiling tokio-macros v2.7.2
   Compiling mio v1.2.3
   Compiling socket2 v0.6.5
   Compiling calternal-path v0.0.1 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-path)
   Compiling bounded-static v0.8.0
   Compiling unsafe-libyaml v0.2.11
   Compiling pulldown-cmark v0.13.4
   Compiling base64 v0.22.1
   Compiling bytes v1.12.1
   Compiling ryu v1.0.23
   Compiling fastrand v2.5.0
   Compiling unicode-width v0.2.2
   Compiling tokio v1.53.1
   Compiling serde_yaml v0.9.34+deprecated
   Compiling tempfile v3.27.0
   Compiling getopts v0.2.24
   Compiling imap-types v2.0.0-alpha.7
   Compiling tracing v0.1.44
   Compiling abnf-core v0.6.0
   Compiling hashify v0.2.9
   Compiling wait-timeout v0.2.1
   Compiling unicase v2.9.0
   Compiling bit-vec v0.8.0
   Compiling ego-tree v0.6.3
   Compiling percent-encoding v2.3.2
   Compiling unicode-properties v0.1.4
   Compiling unicode-casefold v0.2.0
   Compiling fnv v1.0.7
   Compiling quick-error v1.2.3
   Compiling pulldown-cmark-escape v0.11.0
   Compiling calternal-fs v0.1.0 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-fs)
   Compiling scraper v0.18.1
   Compiling rusty-fork v0.3.1
   Compiling bit-set v0.8.0
   Compiling calternal-notes-core v0.1.0 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-notes-core)
   Compiling rand_chacha v0.9.0
   Compiling imap-codec v2.0.0-alpha.9
   Compiling mail-parser v0.11.9
   Compiling rand v0.9.5
   Compiling rand_xorshift v0.4.0
   Compiling regex-syntax v0.8.11
   Compiling unarray v0.1.4
   Compiling similar v3.2.0
   Compiling calternal-imap v0.0.1 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-imap)
   Compiling proptest v1.11.0
    Finished `test` profile [unoptimized + debuginfo] target(s) in 19m 15s
     Running unittests src/lib.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/calternal_imap-62cacbfd75423f87)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/mailboxes.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/mailboxes-0efb2cd3be7499ea)

running 3 tests
test folder_names_cannot_address_another_home_or_hidden_state ... ok
test nested_tags_and_untagged_notes_follow_owner_mapping ... ok
test unicode_mailboxes_and_daily_note_folder_are_reversible ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/mime.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/mime-479a7471591c3826)

running 8 tests
test duplicate_identity_headers_are_refused ... ok
test byte_and_part_limits_are_named_and_enforced ... ok
test native_mac_message_keeps_identity_html_and_raw_bytes ... ok
test oversized_child_headers_are_refused ... ok
test related_image_and_pdf_round_trip_without_changing_wrapper_bytes ... ok
test malformed_or_non_notes_messages_are_refused ... ok
test rendering_enforces_the_same_header_limit_as_parsing ... ok
test rendered_note_carries_subject_date_and_a_full_document ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

     Running tests/projection.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/projection-5e71372548572826)

running 20 tests
test formatted_word_suffix_changes_keep_the_original_delimiters ... ok
test a_block_boundary_edit_is_not_silently_discarded ... ok
test invalid_saved_source_map_is_refused_without_a_panic ... ok
test an_apple_line_break_is_preserved_or_kept_as_a_conflict ... ok
test active_html_is_never_projected_as_active_content ... ok
test an_inserted_empty_paragraph_is_kept_as_a_conflict ... ok
test overlapping_edit_keeps_both_sources_for_a_conflicted_copy ... ok
test apple_title_edit_keeps_table_checklist_and_unknown_bytes ... ok
test prose_insert_cannot_create_an_unrequested_markdown_list ... ok
test mac_typing_at_paragraph_end_merges_around_tables_and_marks ... ok
test unchanged_apple_serialization_preserves_every_byte ... ok
test punctuation_edit_inside_code_does_not_add_prose_escapes ... ok
test punctuation_can_be_appended_to_a_text_run ... ok
test checklist_uses_the_rendered_apple_grammar ... ok
test apple_table_cell_boundary_with_changed_neighbours_stays_conflicted ... ok
test apple_table_cell_suffix_edit_keeps_written_table_separators ... ok
test apple_title_and_div_lines_become_separate_blocks ... ok
test apple_table_cell_prefix_edit_keeps_written_table_separators ... ok
test disjoint_edit_merges_into_current_markdown ... ok
test unedited_projection_is_byte_lossless ... ok

test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.54s

     Running tests/session.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/session-e3eebd9c020743c5)

running 7 tests
test listing_rejects_path_tricks_in_virtual_patterns ... ok
test captured_listing_status_and_uid_search_keep_the_selected_view ... ok
test commands_cannot_fetch_before_selection_or_escape_notes ... ok
test refresh_emits_deletions_before_new_sequence_view ... ok
test idle_wakes_and_preserves_done_with_pipelined_command ... ok
test captured_select_fetch_and_condstore_filter_execute ... ok
test mac_header_fetch_serves_date_size_and_selected_fields ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

     Running tests/submission.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/submission-ace00b33b02a4815)

running 5 tests
test login_challenges_and_wrong_password_fail_closed ... ok
test rejects_plain_impersonation_and_malformed_auth ... ok
test bounds_lines_pipelining_and_partial_line_deadline ... ok
test authenticates_but_refuses_every_mail_command_and_rechecks_revocation ... ok
test auth_exchange_has_one_deadline_and_plain_challenge_works ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/wire.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/wire-e2e1e8d1d7517319)

running 5 tests
test sync_literal_gets_continuation_before_content ... ok
test captured_non_sync_append_and_delete_decode ... ok
test long_unterminated_command_is_bounded ... ok
test oversized_literal_is_refused_before_body_or_continuation ... ok
test captured_select_and_condstore_fetch_decode ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

   Doc-tests calternal_imap

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Cleanup output (verbatim):

     Removed 1374 files, 847.3MiB total

Web build output removed if present. Final tracked status is checked separately before exit. Issues remain open.

Completed the assigned defensive protocol source audit. Branch: job/sec-protocols Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 Head: dc244e8b60384668d8f603dafbf438c6ad475b05 Commits: d5b1f5af4 (findings), a07a92037 (repair links/source pins), dc244e8b6 (verification). Files: audit-findings.md only. No product edits, dependency changes, migrations, pushes or deployment. Final fetch/merge of origin/dev: Already up to date. Built: protocol security inventory with file:line evidence, reasoned impact, repair requirements and regression coverage. Five separate BLOCKER repair issues after duplicate searches: - #785 DAV XML lacks an element-depth bound. - #786 Notes IMAP omits configured IP/User connection permits. - #787 active Notes sessions do not enforce App Password expiry. - #788 Files/Notes SSE can emit paths after authorization ends. - #789 legacy MCP session stream/control access has no authenticated-owner binding (conditional on obtaining the session ID). Known gaps: all five fixes remain for their repair jobs. Findings are source-based; no crash threshold or live exploitation measured. No real-server adversarial or production-proxy framing run. Committed #486 is not yet mounted as Mail in the listener; re-audit the finished Mail proxy. No inbound CardDAV server found in audited source. Initialization/session quotas and DAV body-duration bounds need follow-up validation. Existing tests validate protocol controls, not repair of P1–P5. Decisions: followed the explicit protocol audit scope despite #663 being a performance issue and §58 being a queued performance section; kept product edits out of this read-mostly job; marked concrete authorization gaps and input-dependent XML stack exhaustion risks as blockers without claiming a reproduced crash. No new user-facing feature, route or background job was added, so no benchmark profile or UI screenshots apply. Verification: cargo fmt --check and git diff --check exited 0 with no output. Scratch SQLite contract check passed for the expiry predicates using synthetic rows. cargo test -p calternal-imap --locked exited 0: 48 tests passed. No Rust/web crate changed, so no crate clippy, server-route or web gates were added. Cargo environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR in this worktree; preset CARGO_TARGET_DIR preserved. Verbatim cargo test output: ```text Compiling proc-macro2 v1.0.107 Compiling quote v1.0.47 Compiling unicode-ident v1.0.26 Compiling cfg-if v1.0.5 Compiling libc v0.2.189 Compiling zerocopy v0.8.58 Compiling siphasher v1.0.3 Compiling getrandom v0.2.17 Compiling siphasher v0.3.11 Compiling ppv-lite86 v0.2.21 Compiling rand_core v0.6.4 Compiling phf_shared v0.11.3 Compiling phf_shared v0.10.0 Compiling rand_chacha v0.3.1 Compiling autocfg v1.5.1 Compiling serde_core v1.0.229 Compiling syn v3.0.6 Compiling syn v2.0.119 Compiling rand v0.8.8 Compiling num-traits v0.2.19 Compiling getrandom v0.3.4 Compiling serde v1.0.229 Compiling smallvec v1.16.1 Compiling phf_generator v0.11.3 Compiling phf_generator v0.10.0 Compiling bitflags v2.13.2 Compiling once_cell v1.21.4 Compiling phf_codegen v0.10.0 Compiling serde_derive v1.0.229 Compiling new_debug_unreachable v1.0.6 Compiling parking_lot_core v0.9.12 Compiling shlex v2.0.1 Compiling itoa v1.0.18 Compiling memchr v2.8.3 Compiling scopeguard v1.2.0 Compiling rustix v1.1.5 Compiling find-msvc-tools v0.1.13 Compiling syn v1.0.109 Compiling iana-time-zone v0.1.65 Compiling lock_api v0.4.14 Compiling cc v1.4.7 Compiling chrono v0.4.45 Compiling string_cache_codegen v0.5.4 Compiling log v0.4.34 Compiling hashbrown v0.17.1 Compiling thiserror v2.0.21 Compiling linux-raw-sys v0.12.1 Compiling equivalent v1.0.2 Compiling precomputed-hash v0.1.1 Compiling mac v0.1.1 Compiling indexmap v2.14.2 Compiling futf v0.1.5 Compiling phf v0.10.1 Compiling markup5ever v0.11.0 Compiling blake3 v1.8.7 Compiling parking_lot v0.12.5 Compiling phf_macros v0.11.3 Compiling thiserror-impl v2.0.21 Compiling dtoa v1.0.11 Compiling zmij v1.0.23 Compiling utf-8 v0.7.6 Compiling getrandom v0.4.3 Compiling version_check v0.9.5 Compiling dtoa-short v0.3.5 Compiling tendril v0.4.3 Compiling html5ever v0.26.0 Compiling string_cache v0.8.9 Compiling ahash v0.8.12 Compiling phf v0.11.3 Compiling rand_core v0.9.5 Compiling selectors v0.25.0 Compiling cssparser-macros v0.6.1 Compiling phf_shared v0.12.1 Compiling errno v0.3.14 Compiling unicode-joining-type v1.0.0 Compiling tinyvec v1.13.3 Compiling serde_json v1.0.151 Compiling minimal-lexical v0.2.1 Compiling stable_deref_trait v1.2.1 Compiling constant_time_eq v0.4.2 Compiling cpufeatures v0.3.1 Compiling chrono-tz v0.10.4 Compiling pin-project-lite v0.2.17 Compiling arrayvec v0.7.8 Compiling byteorder v1.5.0 Compiling servo_arc v0.3.0 Compiling nom v7.1.3 Compiling fxhash v0.2.1 Compiling unicode-normalization v0.1.25 Compiling signal-hook-registry v1.4.8 Compiling phf v0.12.1 Compiling cssparser v0.31.2 Compiling tracing-core v0.1.36 Compiling derive_more v0.99.20 Compiling tracing-attributes v0.1.31 Compiling bounded-static-derive v0.8.0 Compiling tokio-macros v2.7.2 Compiling mio v1.2.3 Compiling socket2 v0.6.5 Compiling calternal-path v0.0.1 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-path) Compiling bounded-static v0.8.0 Compiling unsafe-libyaml v0.2.11 Compiling pulldown-cmark v0.13.4 Compiling base64 v0.22.1 Compiling bytes v1.12.1 Compiling ryu v1.0.23 Compiling fastrand v2.5.0 Compiling unicode-width v0.2.2 Compiling tokio v1.53.1 Compiling serde_yaml v0.9.34+deprecated Compiling tempfile v3.27.0 Compiling getopts v0.2.24 Compiling imap-types v2.0.0-alpha.7 Compiling tracing v0.1.44 Compiling abnf-core v0.6.0 Compiling hashify v0.2.9 Compiling wait-timeout v0.2.1 Compiling unicase v2.9.0 Compiling bit-vec v0.8.0 Compiling ego-tree v0.6.3 Compiling percent-encoding v2.3.2 Compiling unicode-properties v0.1.4 Compiling unicode-casefold v0.2.0 Compiling fnv v1.0.7 Compiling quick-error v1.2.3 Compiling pulldown-cmark-escape v0.11.0 Compiling calternal-fs v0.1.0 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-fs) Compiling scraper v0.18.1 Compiling rusty-fork v0.3.1 Compiling bit-set v0.8.0 Compiling calternal-notes-core v0.1.0 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-notes-core) Compiling rand_chacha v0.9.0 Compiling imap-codec v2.0.0-alpha.9 Compiling mail-parser v0.11.9 Compiling rand v0.9.5 Compiling rand_xorshift v0.4.0 Compiling regex-syntax v0.8.11 Compiling unarray v0.1.4 Compiling similar v3.2.0 Compiling calternal-imap v0.0.1 (/home/kayg/Developer/calternal-wt/sec-protocols/crates/calternal-imap) Compiling proptest v1.11.0 Finished `test` profile [unoptimized + debuginfo] target(s) in 19m 15s Running unittests src/lib.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/calternal_imap-62cacbfd75423f87) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/mailboxes.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/mailboxes-0efb2cd3be7499ea) running 3 tests test folder_names_cannot_address_another_home_or_hidden_state ... ok test nested_tags_and_untagged_notes_follow_owner_mapping ... ok test unicode_mailboxes_and_daily_note_folder_are_reversible ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/mime.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/mime-479a7471591c3826) running 8 tests test duplicate_identity_headers_are_refused ... ok test byte_and_part_limits_are_named_and_enforced ... ok test native_mac_message_keeps_identity_html_and_raw_bytes ... ok test oversized_child_headers_are_refused ... ok test related_image_and_pdf_round_trip_without_changing_wrapper_bytes ... ok test malformed_or_non_notes_messages_are_refused ... ok test rendering_enforces_the_same_header_limit_as_parsing ... ok test rendered_note_carries_subject_date_and_a_full_document ... ok test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s Running tests/projection.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/projection-5e71372548572826) running 20 tests test formatted_word_suffix_changes_keep_the_original_delimiters ... ok test a_block_boundary_edit_is_not_silently_discarded ... ok test invalid_saved_source_map_is_refused_without_a_panic ... ok test an_apple_line_break_is_preserved_or_kept_as_a_conflict ... ok test active_html_is_never_projected_as_active_content ... ok test an_inserted_empty_paragraph_is_kept_as_a_conflict ... ok test overlapping_edit_keeps_both_sources_for_a_conflicted_copy ... ok test apple_title_edit_keeps_table_checklist_and_unknown_bytes ... ok test prose_insert_cannot_create_an_unrequested_markdown_list ... ok test mac_typing_at_paragraph_end_merges_around_tables_and_marks ... ok test unchanged_apple_serialization_preserves_every_byte ... ok test punctuation_edit_inside_code_does_not_add_prose_escapes ... ok test punctuation_can_be_appended_to_a_text_run ... ok test checklist_uses_the_rendered_apple_grammar ... ok test apple_table_cell_boundary_with_changed_neighbours_stays_conflicted ... ok test apple_table_cell_suffix_edit_keeps_written_table_separators ... ok test apple_title_and_div_lines_become_separate_blocks ... ok test apple_table_cell_prefix_edit_keeps_written_table_separators ... ok test disjoint_edit_merges_into_current_markdown ... ok test unedited_projection_is_byte_lossless ... ok test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.54s Running tests/session.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/session-e3eebd9c020743c5) running 7 tests test listing_rejects_path_tricks_in_virtual_patterns ... ok test captured_listing_status_and_uid_search_keep_the_selected_view ... ok test commands_cannot_fetch_before_selection_or_escape_notes ... ok test refresh_emits_deletions_before_new_sequence_view ... ok test idle_wakes_and_preserves_done_with_pipelined_command ... ok test captured_select_fetch_and_condstore_filter_execute ... ok test mac_header_fetch_serves_date_size_and_selected_fields ... ok test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s Running tests/submission.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/submission-ace00b33b02a4815) running 5 tests test login_challenges_and_wrong_password_fail_closed ... ok test rejects_plain_impersonation_and_malformed_auth ... ok test bounds_lines_pipelining_and_partial_line_deadline ... ok test authenticates_but_refuses_every_mail_command_and_rechecks_revocation ... ok test auth_exchange_has_one_deadline_and_plain_challenge_works ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/wire.rs (/mnt/hdd/targets/jobs/sec-protocols/debug/deps/wire-e2e1e8d1d7517319) running 5 tests test sync_literal_gets_continuation_before_content ... ok test captured_non_sync_append_and_delete_decode ... ok test long_unterminated_command_is_bounded ... ok test oversized_literal_is_refused_before_body_or_continuation ... ok test captured_select_and_condstore_fetch_decode ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s Doc-tests calternal_imap running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Cleanup output (verbatim): ```text Removed 1374 files, 847.3MiB total ``` Web build output removed if present. Final tracked status is checked separately before exit. Issues remain open.
Author
Owner

SQLite architecture audit delivered on job/perf-arch-db. Head: 4254fe11cd. Base origin/dev: c4a61e8cf0. Four atomic audit commits; no product/API/dependency/migration edits, push, deployment, or issue closure. The required final git fetch origin and git merge origin/dev ran once and returned Already up to date.

Built/files: audit-findings.md records the full table list, query/transaction/migration review, evidence, issue ownership and limits. audit-sqlite.py is a standard-library-only source inventory and disposable SQLite plan probe. It never opens a User Index. Generated inventories, plans and scratch data remain under ignored artifacts/db-audit/. No review artifacts are committed.

Coverage: base has 96 migrations, 135 tables including 13 FTS shadow tables, and 1,566 candidate SQL call sites including tests. Queued merge-round-7a 2f4482ded0 has 109 migrations, 146 tables and 1,772 candidate sites. Separate Semantic/CLIP/client-journal schemas were read in source; Money has no SQLite schema and its projection is already #687. Read DESIGN/CLAUDE/CONTEXT, queued DESIGN section 58, the round-7b list, and relevant changes in ryw-653, perf-cache-665, perf-mut-667, perf-snap-666 and perf-495. The new 64-reader/cache limits and bounded-memory Search/Photo work are credited, not refiled.

New findings, each with pinned source, impact, concrete fix and regression tests after duplicate search:

  • #823: move WAL checkpoint work off interactive commits. Default commit-triggered checkpointing can occupy the sole writer and do HDD sync/scattered writes.
  • BLOCKER #824: acknowledged Security state is not power-loss durable under WAL/NORMAL. Auth uses that writer. SQLite documents possible committed-state rollback after power loss/hard reset; no rollback incident or corruption was reproduced. Normal process-restart durability does not resolve this risk.
  • #825: Mail folder SQL visits/sorts the full folder before LIMIT. Coordinate with #685's response/cursor contract; preserve received-time order and preferred duplicate UID semantics.
  • #832: Search publication still copies complete live/staged generations while holding the shared writer after #496's memory fix. Coordinate generation publication with #444's physical separation; retain bounded-memory staging.

Evidence added to existing owners instead of duplicates: #703 Note NOCASE index mismatch and Note/Task OFFSET; #682 full-day Photo ranking; #681 mandatory Files counts and whole-owner Share badges; #692/#698 writer-pool Auth reads; #757 expunge writer occupancy/account-leading cleanup seeks; #748 runtime snapshot writer occupancy; #678 narrow Calendar history scan; #679 parsing inside Calendar sync writer transaction; #784 Tag count/list collation inconsistency. Corrected a transposed source-line label in my #698 comment.

Local query-work evidence on synthetic data, repeated on both schemas:

  • Note title first 101 rows: about 1,201,000 SQLite VM instructions with temporary ORDER BY; matching NOCASE index about 1,000 with identical ordered result hash.
  • Note recent offset 90,000: about 361,000; first page about 1,000. A unique/non-null edited seek hypothesis is below 1,000; production ties/nulls remain acceptance work.
  • Mail folder first 101 rows: about 8,001,000; after ANALYZE about 8,502,000. Existing unified Inbox on the same 100k corpus: about 10,000. These are work counts, not endpoint speedup ratios.
  • Photos 100 tiles from a 10k day: about 1,003,000; single-day early-LIMIT hypothesis about 4,000. Multi-day/access/freshness semantics must stay correct.
  • Files 101-row name page: about 1,000; mandatory filtered 100k-folder count about 1,400,000.
  • Calendar one Event from 100k finite history: about 1,500,000 with history scan and temporary sort. Unknown/recurring series need separate tests.

No latency, average CPU/RSS, production HDD budget or baseline regression claim is made. There is no matching VM-instruction metric in docs/perf/baseline.json; #549 remains the HDD latency reference. Python SQLite is 3.53.3; locked libsqlite3-sys 0.37.0 bundles 3.51.3. Cargo search verified sqlx 0.9.0 and latest libsqlite3-sys 0.38.2; no dependency changed. Recheck the production planner before implementing fixes.

Gates/evidence (verbatim stdout):

Inventory c4a61e8cf090170f35b1bed3350d9de20c83ecd5: 96 migrations, 135 tables, 1566 SQL call sites; SQLite 3.53.3
Inventory 2f4482ded066d9c5d9c59130377907f7fd2916c9: 109 migrations, 146 tables, 1772 SQL call sites; SQLite 3.53.3
Audit evidence: PASS; 17 plan probes agree across base and round-7a; all Note index result checks passed

cargo fmt --check exited 0 with no output. Python byte compilation and git diff --check exited 0 with no output. No Rust crate or web source changed: per-crate clippy/test and web gates were not run. No API changed: no real-server adversarial round or screenshots apply. Existing test expectations were untouched. Comments/docs in both changed files were re-read. Working tree is clean.

Cleanup (verbatim):

     Removed 1 file, 356B total

No web build output was created or present. CARGO_TARGET_DIR was not overridden.

Known gaps: lexical inventory is not a complete Rust/dynamic SQL parser. Only listed hot-path fixtures are populated; other tables are empty. No populated FK-enabled upgrade, sqlite-vec runtime, power-loss injection, sparse-folder/multi-User burst, wide-payload or HDD latency/CPU/RSS test is claimed. These are concrete follow-up acceptance tests, not passing audit gates. Large source migrations and runtime snapshots were reviewed; no applied migration/checksum was changed. Queued receipt migration 0007 conflicts with 7a's core 0007 and needs owner renumbering before integration.

Decisions: use local deterministic plans/work counts for source claims; reserve the perf VM for claims needing latency. Keep large generated evidence ignored and commit the reproducible tool plus compact report. Reuse existing per-Tab issues for broad rules; create distinct shared/query-work issues only. No product design decision was changed. #824's stronger durability policy is a proposed fix requiring owner confirmation during its implementation, not a silent change here.

UX gaps closed / UX gaps left: not applicable; no UI feature changed.

SQLite architecture audit delivered on job/perf-arch-db. Head: 4254fe11cde56776a28822bd6819beecf5866788. Base origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Four atomic audit commits; no product/API/dependency/migration edits, push, deployment, or issue closure. The required final git fetch origin and git merge origin/dev ran once and returned Already up to date. Built/files: audit-findings.md records the full table list, query/transaction/migration review, evidence, issue ownership and limits. audit-sqlite.py is a standard-library-only source inventory and disposable SQLite plan probe. It never opens a User Index. Generated inventories, plans and scratch data remain under ignored artifacts/db-audit/. No review artifacts are committed. Coverage: base has 96 migrations, 135 tables including 13 FTS shadow tables, and 1,566 candidate SQL call sites including tests. Queued merge-round-7a 2f4482ded066d9c5d9c59130377907f7fd2916c9 has 109 migrations, 146 tables and 1,772 candidate sites. Separate Semantic/CLIP/client-journal schemas were read in source; Money has no SQLite schema and its projection is already #687. Read DESIGN/CLAUDE/CONTEXT, queued DESIGN section 58, the round-7b list, and relevant changes in ryw-653, perf-cache-665, perf-mut-667, perf-snap-666 and perf-495. The new 64-reader/cache limits and bounded-memory Search/Photo work are credited, not refiled. New findings, each with pinned source, impact, concrete fix and regression tests after duplicate search: - #823: move WAL checkpoint work off interactive commits. Default commit-triggered checkpointing can occupy the sole writer and do HDD sync/scattered writes. - BLOCKER #824: acknowledged Security state is not power-loss durable under WAL/NORMAL. Auth uses that writer. SQLite documents possible committed-state rollback after power loss/hard reset; no rollback incident or corruption was reproduced. Normal process-restart durability does not resolve this risk. - #825: Mail folder SQL visits/sorts the full folder before LIMIT. Coordinate with #685's response/cursor contract; preserve received-time order and preferred duplicate UID semantics. - #832: Search publication still copies complete live/staged generations while holding the shared writer after #496's memory fix. Coordinate generation publication with #444's physical separation; retain bounded-memory staging. Evidence added to existing owners instead of duplicates: #703 Note NOCASE index mismatch and Note/Task OFFSET; #682 full-day Photo ranking; #681 mandatory Files counts and whole-owner Share badges; #692/#698 writer-pool Auth reads; #757 expunge writer occupancy/account-leading cleanup seeks; #748 runtime snapshot writer occupancy; #678 narrow Calendar history scan; #679 parsing inside Calendar sync writer transaction; #784 Tag count/list collation inconsistency. Corrected a transposed source-line label in my #698 comment. Local query-work evidence on synthetic data, repeated on both schemas: - Note title first 101 rows: about 1,201,000 SQLite VM instructions with temporary ORDER BY; matching NOCASE index about 1,000 with identical ordered result hash. - Note recent offset 90,000: about 361,000; first page about 1,000. A unique/non-null edited seek hypothesis is below 1,000; production ties/nulls remain acceptance work. - Mail folder first 101 rows: about 8,001,000; after ANALYZE about 8,502,000. Existing unified Inbox on the same 100k corpus: about 10,000. These are work counts, not endpoint speedup ratios. - Photos 100 tiles from a 10k day: about 1,003,000; single-day early-LIMIT hypothesis about 4,000. Multi-day/access/freshness semantics must stay correct. - Files 101-row name page: about 1,000; mandatory filtered 100k-folder count about 1,400,000. - Calendar one Event from 100k finite history: about 1,500,000 with history scan and temporary sort. Unknown/recurring series need separate tests. No latency, average CPU/RSS, production HDD budget or baseline regression claim is made. There is no matching VM-instruction metric in docs/perf/baseline.json; #549 remains the HDD latency reference. Python SQLite is 3.53.3; locked libsqlite3-sys 0.37.0 bundles 3.51.3. Cargo search verified sqlx 0.9.0 and latest libsqlite3-sys 0.38.2; no dependency changed. Recheck the production planner before implementing fixes. Gates/evidence (verbatim stdout): ``` Inventory c4a61e8cf090170f35b1bed3350d9de20c83ecd5: 96 migrations, 135 tables, 1566 SQL call sites; SQLite 3.53.3 Inventory 2f4482ded066d9c5d9c59130377907f7fd2916c9: 109 migrations, 146 tables, 1772 SQL call sites; SQLite 3.53.3 Audit evidence: PASS; 17 plan probes agree across base and round-7a; all Note index result checks passed ``` cargo fmt --check exited 0 with no output. Python byte compilation and git diff --check exited 0 with no output. No Rust crate or web source changed: per-crate clippy/test and web gates were not run. No API changed: no real-server adversarial round or screenshots apply. Existing test expectations were untouched. Comments/docs in both changed files were re-read. Working tree is clean. Cleanup (verbatim): ``` Removed 1 file, 356B total ``` No web build output was created or present. CARGO_TARGET_DIR was not overridden. Known gaps: lexical inventory is not a complete Rust/dynamic SQL parser. Only listed hot-path fixtures are populated; other tables are empty. No populated FK-enabled upgrade, sqlite-vec runtime, power-loss injection, sparse-folder/multi-User burst, wide-payload or HDD latency/CPU/RSS test is claimed. These are concrete follow-up acceptance tests, not passing audit gates. Large source migrations and runtime snapshots were reviewed; no applied migration/checksum was changed. Queued receipt migration 0007 conflicts with 7a's core 0007 and needs owner renumbering before integration. Decisions: use local deterministic plans/work counts for source claims; reserve the perf VM for claims needing latency. Keep large generated evidence ignored and commit the reproducible tool plus compact report. Reuse existing per-Tab issues for broad rules; create distinct shared/query-work issues only. No product design decision was changed. #824's stronger durability policy is a proposed fix requiring owner confirmation during its implementation, not a silent change here. UX gaps closed / UX gaps left: not applicable; no UI feature changed.
Author
Owner

Static audit evidence for the older performance-gate text:

DESIGN §18 says its LAN performance budgets are enforced by a CI suite that fails on regressions at docs/DESIGN.md:433-434. CLAUDE.md:86-95 says performance review is periodic, never a merge gate; bench/run.sh and the weekly timer record results and file issues.

Expected: align the older §18 wording with the current owner rule. A periodic regression report can create a follow-up issue without making performance a merge gate.

Static audit evidence for the older performance-gate text: DESIGN §18 says its LAN performance budgets are enforced by a CI suite that fails on regressions at docs/DESIGN.md:433-434. CLAUDE.md:86-95 says performance review is periodic, never a merge gate; bench/run.sh and the weekly timer record results and file issues. Expected: align the older §18 wording with the current owner rule. A periodic regression report can create a follow-up issue without making performance a merge gate.
Author
Owner

Design-sync #864 corrected DESIGN §18 on job/design-sync, commit 8dadf0963.

Old evidence: docs/DESIGN.md:433-434 required a CI performance suite that fails on regression. CLAUDE.md:86-95 requires periodic review and says measured regressions do not block merges.

The text now records weekly bench/run.sh review, the baseline, Sunday 03:00 IST, committed results and an issue for each regression over the threshold. Mechanical architecture and contract guards still fail the gates. Numeric targets stay. Review idea: compare §18 with the periodic-review owner rule and keep measured regressions separate from mechanical guard failures. No builds or tests ran (LIGHT job). The base has no §58; section-number reconciliation remains for the merge round.

Design-sync #864 corrected DESIGN §18 on `job/design-sync`, commit `8dadf0963`. Old evidence: docs/DESIGN.md:433-434 required a CI performance suite that fails on regression. CLAUDE.md:86-95 requires periodic review and says measured regressions do not block merges. The text now records weekly bench/run.sh review, the baseline, Sunday 03:00 IST, committed results and an issue for each regression over the threshold. Mechanical architecture and contract guards still fail the gates. Numeric targets stay. Review idea: compare §18 with the periodic-review owner rule and keep measured regressions separate from mechanical guard failures. No builds or tests ran (LIGHT job). The base has no §58; section-number reconciliation remains for the merge round.
Sign in to join this conversation.
No labels
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#663
No description provided.