BLOCKER: revoke browser push authority when a User session ends #759

Open
opened 2026-10-02 13:09:41 +00:00 by kayg · 27 comments
Owner

Browser audit follow-up from #663. Audited dev: c4a61e8cf0.

B1 — Push survives session end

Status: confirmed by code trace; duplicate search complete; filed by sec-browser audit.
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.

Evidence is a defensive code trace; no production access or hostile payload was used.

Browser audit follow-up from #663. Audited dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. ### B1 — Push survives session end Status: confirmed by code trace; duplicate search complete; filed by sec-browser audit. 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. Evidence is a defensive code trace; no production access or hostile payload was used.
Author
Owner

Started browserfix for #759, #765, #766 and #767. Branch: job/browserfix. Base: 2f4482ded0 (job/merge-round-7a). Read repository rules, DESIGN §§21, 22, 45, 54, 57, and audit-findings.md. I will trace each lifecycle, add regression tests and make atomic commits. No push or deploy.

Started browserfix for #759, #765, #766 and #767. Branch: job/browserfix. Base: 2f4482ded066d9c5d9c59130377907f7fd2916c9 (job/merge-round-7a). Read repository rules, DESIGN §§21, 22, 45, 54, 57, and audit-findings.md. I will trace each lifecycle, add regression tests and make atomic commits. No push or deploy.
Author
Owner

Root causes confirmed on 2f4482ded0: subscription rows have no session identity; pending_pushes does not check Auth; versioned thumbnail 200/304 responses use private, max-age=31536000, immutable; imageResolver returns external HTTP(S) URLs directly. The queued readerCache on job/mailhtml-726 has no clear/cancellation path. A new push regression failed because disablePush skips unsubscribe after a 401; the local cleanup fix now passes all four push tests.

Decisions: bind push to the existing hash-only Auth session key without changing the public request context fields. Keep thumbnail URL versions and ETags, but send private, no-store for both 200 and 304 (#765 explicitly changes the old cache-policy expectation). No proxy exists on #726 yet; add one shared server route using the existing calternal-plugin outbound resolver, with pinned DNS, bounded raster responses and no browser notice. Provide the queued Mail cache fix as compatible source in this branch; do not merge the unrelated Mail UI branch.

Root causes confirmed on 2f4482ded066d9c5d9c59130377907f7fd2916c9: subscription rows have no session identity; pending_pushes does not check Auth; versioned thumbnail 200/304 responses use private, max-age=31536000, immutable; imageResolver returns external HTTP(S) URLs directly. The queued readerCache on job/mailhtml-726 has no clear/cancellation path. A new push regression failed because disablePush skips unsubscribe after a 401; the local cleanup fix now passes all four push tests. Decisions: bind push to the existing hash-only Auth session key without changing the public request context fields. Keep thumbnail URL versions and ETags, but send private, no-store for both 200 and 304 (#765 explicitly changes the old cache-policy expectation). No proxy exists on #726 yet; add one shared server route using the existing calternal-plugin outbound resolver, with pinned DNS, bounded raster responses and no browser notice. Provide the queued Mail cache fix as compatible source in this branch; do not merge the unrelated Mail UI branch.
Author
Owner

Starting the browser security fixes for #759, #765, #766 and #767.

Branch: job/browserfix
Base: job/merge-round-7a at 2f4482ded066d9c5d9c59130377907f7fd2916c9
Current HEAD: 39211c69f604f3809baf7f7f9c62b811641ac19d

The worktree already has uncommitted changes for these fixes and regression tests. I am preserving that state and tracing each path before adding or changing code.

Starting the browser security fixes for #759, #765, #766 and #767. Branch: `job/browserfix` Base: `job/merge-round-7a` at `2f4482ded066d9c5d9c59130377907f7fd2916c9` Current HEAD: `39211c69f604f3809baf7f7f9c62b811641ac19d` The worktree already has uncommitted changes for these fixes and regression tests. I am preserving that state and tracing each path before adding or changing code.
Author
Owner

browserfix #759 / #765 / #766 / #767 — final status

Implemented and committed on job/browserfix at 2f5a6b9497ab6a395b2fc51bb2dbd1cf2349d782:

  • Push subscriptions bind to the exact revocable session hash. Session revocation/deletion removes server authority and queued deliveries; the service worker checks the same session before showing or opening a notification, and sign-out clears local notifications/subscriptions even after session expiry.
  • Shared-item thumbnails use private, no-store. Appearance now requests v=2 so a browser cannot reuse a still-fresh v=1 image from the previous one-year immutable policy; legacy v=1 remains accepted without caching. Share access-change events clear listing caches.
  • Notes external images use the new shared authenticated raster proxy. It rejects private destinations, re-resolves and pins every redirect, caps redirects, bytes, time and concurrency, and refuses active content. I reported the shared route to #726 for Mail reuse.
  • Mail body/detail/thread/list/shell caches are User and session-epoch scoped. Session end clears them, aborts reads and rejects late results.
  • Added regression tests, a hostile image-proxy probe and a local performance profile.

Files include crates/calternal-plugin/src/lib.rs, notification plugin routes/store/push and migration 0005_push_sessions.sql, Files thumbnails and tests, crates/calternal-server/src/image_proxy.rs and wiring, contracts/openapi.json, Notes/Mail/Files browser code and tests, E2E probes, and bench/browserfix-image-proxy.mjs.

Verification

  • cargo fmt --check: exit 0; stdout and stderr were empty.
  • bun run check: svelte-check found 0 errors and 0 warnings (completed before the final v=2 URL adjustment; the later production build included that adjustment).
  • Changed-path Vitest run: Test Files 5 passed (5) and Tests 38 passed (38).
  • bun run build: completed successfully (Wrote site to "build", ✔ done). Build output was removed afterward.
  • Full bun run test did not complete. It accumulated test timeouts under severe shared-host load; I stopped it at the job limit. Captured terminal result: error: script "test" exited with code 130.
  • cargo fmt --check passed. The in-flight focused Cargo test was still compiling dependencies at the 3-hour cutoff; no Rust test result or Clippy result was produced. Per-crate Clippy/tests, live cross-User matrix, image-proxy adversarial round, production-server browser tests/screenshots, and the performance run remain unverified. The server binary was not built. Host load average was 109.28 / 118.65 / 130.65 at 16:21 UTC.
  • origin/dev and job/merge-round-7a were merged once. origin/dev notifications migrations ended at 0004; 0005 is free.

UX gaps

  • Closed: end-of-session push notices and delayed clicks are fenced; thumbnail references are cache-busted and server responses re-check access; Notes images load through the server without notices; Mail late body reads cannot repopulate another User's cache.
  • Left: required real-server two-User/browser and cross-User checks, and macOS-emulated 390/820/1440 light/dark screenshot attachments, could not run because the local server binary was not produced.

Decisions not specified in DESIGN.md

  • Use a hash of the browser session as push authority, never the cookie token; App Passwords cannot subscribe.
  • Use v=2 as the thumbnail URL cache-buster while preserving v=1 compatibility.
  • The shared proxy accepts PNG, JPEG, GIF, WebP and AVIF; limits bodies to 5 MiB, redirects to three, active requests to eight, and the overall request to ten seconds.
  • OpenAPI entries were updated with the route and cache policy, but could not be regenerated from the compiled server before the cutoff.

No push, deploy or merge to dev was performed. This branch is not ready to merge until the listed Rust gates and live-server checks complete.

## browserfix #759 / #765 / #766 / #767 — final status Implemented and committed on `job/browserfix` at `2f5a6b9497ab6a395b2fc51bb2dbd1cf2349d782`: - Push subscriptions bind to the exact revocable session hash. Session revocation/deletion removes server authority and queued deliveries; the service worker checks the same session before showing or opening a notification, and sign-out clears local notifications/subscriptions even after session expiry. - Shared-item thumbnails use `private, no-store`. Appearance now requests `v=2` so a browser cannot reuse a still-fresh `v=1` image from the previous one-year immutable policy; legacy `v=1` remains accepted without caching. Share access-change events clear listing caches. - Notes external images use the new shared authenticated raster proxy. It rejects private destinations, re-resolves and pins every redirect, caps redirects, bytes, time and concurrency, and refuses active content. I reported the shared route to #726 for Mail reuse. - Mail body/detail/thread/list/shell caches are User and session-epoch scoped. Session end clears them, aborts reads and rejects late results. - Added regression tests, a hostile image-proxy probe and a local performance profile. Files include `crates/calternal-plugin/src/lib.rs`, notification plugin routes/store/push and migration `0005_push_sessions.sql`, Files thumbnails and tests, `crates/calternal-server/src/image_proxy.rs` and wiring, `contracts/openapi.json`, Notes/Mail/Files browser code and tests, E2E probes, and `bench/browserfix-image-proxy.mjs`. ### Verification - `cargo fmt --check`: exit 0; stdout and stderr were empty. - `bun run check`: `svelte-check found 0 errors and 0 warnings` (completed before the final v=2 URL adjustment; the later production build included that adjustment). - Changed-path Vitest run: `Test Files 5 passed (5)` and `Tests 38 passed (38)`. - `bun run build`: completed successfully (`Wrote site to "build"`, `✔ done`). Build output was removed afterward. - Full `bun run test` did not complete. It accumulated test timeouts under severe shared-host load; I stopped it at the job limit. Captured terminal result: `error: script "test" exited with code 130`. - `cargo fmt --check` passed. The in-flight focused Cargo test was still compiling dependencies at the 3-hour cutoff; no Rust test result or Clippy result was produced. Per-crate Clippy/tests, live cross-User matrix, image-proxy adversarial round, production-server browser tests/screenshots, and the performance run remain unverified. The server binary was not built. Host load average was 109.28 / 118.65 / 130.65 at 16:21 UTC. - `origin/dev` and `job/merge-round-7a` were merged once. `origin/dev` notifications migrations ended at `0004`; `0005` is free. ### UX gaps - Closed: end-of-session push notices and delayed clicks are fenced; thumbnail references are cache-busted and server responses re-check access; Notes images load through the server without notices; Mail late body reads cannot repopulate another User's cache. - Left: required real-server two-User/browser and cross-User checks, and macOS-emulated 390/820/1440 light/dark screenshot attachments, could not run because the local server binary was not produced. ### Decisions not specified in DESIGN.md - Use a hash of the browser session as push authority, never the cookie token; App Passwords cannot subscribe. - Use `v=2` as the thumbnail URL cache-buster while preserving `v=1` compatibility. - The shared proxy accepts PNG, JPEG, GIF, WebP and AVIF; limits bodies to 5 MiB, redirects to three, active requests to eight, and the overall request to ten seconds. - OpenAPI entries were updated with the route and cache policy, but could not be regenerated from the compiled server before the cutoff. No push, deploy or merge to `dev` was performed. This branch is not ready to merge until the listed Rust gates and live-server checks complete.
Author
Owner

Independent read-only review of job/browserfix at 2f5a6b9497ab6a395b2fc51bb2dbd1cf2349d782: changes required.

Review branch: job/rev2-browserfix. Base: 440e19dce23040ac8ebaae88f0469b6535b1afcb. Review head: f56d248146226fe55f9d96a43472cd8854e7b663.

Committed files: review-browserfix.md (full review) and audit-findings.md (evidence log). No product code changed.

  1. P1 — copied push tasks survive revocation (#759). crates/plugins/notifications/src/store.rs:701 checks authority before it returns owned tasks. crates/plugins/notifications/src/push.rs:120 and :157 send those tasks without a new authority check after endpoint validation. Revocation can delete the subscription while the task waits, but the copied private title and body can still be sent. The worker check limits display; no cross-User display from this race is proved. Also, store.rs:782 deletes by User and Installation only, so an old failed send can delete a new same-User binding. Fix: bind dispatch and result writes to the exact session and subscription generation; cancel queued sends and check authority at the transport boundary. Use an opaque wake-up and authenticated content read for the unavoidable network race. Test: hold a fake transport, revoke, then release; assert no private delivery. Rebind and fail the old task; assert that the new binding and another live Installation remain active.

  2. P1 — remote revoke leaves shown private notices (#759). apps/web/src/service-worker.ts:67 closes old notices only after a later failed push check; :138 requires a local session-end message. Neither happens on remote revoke or natural expiry while the app is closed. Server cleanup stops future pushes. At apps/web/src/lib/userStorage.ts:223, a fresh sign-in document without a previous User hint can sign in B without session-end cleanup. A's old system notice can remain with B's push off. Fix: reconcile shown notices at sign-in and worker wake-up; add a revoke cleanup signal where possible, and use safe system text when offline or expiry prevents cleanup. Test: show A's notice, close app windows, revoke or expire A, then sign in B with push off. Assert that A's notice is gone without a new private push, and keep a second live Installation working.

  3. P2 — busy proxy slots leave valid images missing (#766/#726). crates/calternal-server/src/image_proxy.rs:51 shares eight immediate-admission slots across all Users. Overflow gets 429. apps/web/src/lib/notes/NoteImageView.svelte:49 makes that a permanent missing-image state with no retry. Fix: retain the resource bounds; add bounded fair admission and retry temporary failures in the shared loader, without external browser fallback. Test: hold eight benign image reads, request one more, release the held reads, and assert that the extra image appears without reopening the Note. Include two Users. This is a functional admission failure, not a SLOW-only finding.

The thumbnail no-store change, v=2 URL migration, shared outbound resolver and Mail cache epoch cleanup are consistent with their issue requirements by code trace. No unrelated weakened test assertion was found in the relevant history. The thumbnail immutable/304 and Notes direct-origin expectations change as #765 and #766 require. The Notes browser test fulfills the proxy response in Playwright; it does not test successful server transport.

Duplicate searches found existing owners #759, #766 and #726. All findings are in this branch, so no separate issue was created. Keep these issues open.

Validation: LIGHT rules forbid builds, tests, servers, browsers and performance runs. There is no Rust or web gate output. git diff --check exited 0 with no output. The requested fetch and merge of origin/dev completed; merge output verbatim:

Already up to date.

Known gaps: static review only; runtime, transport and OS notification checks remain for a build job. UX gaps closed: none in this read-only job. UX gaps left: F2 and F3.

Decisions: review only the fixed target SHA and its browser-fix commits, with related final code; exclude the author's report and uncommitted work. DESIGN §58 at this SHA is agent discovery; use §18 for interactive budgets and §§21, 54 and 57 for authority and cleanup. No product design decision was made.

Independent read-only review of `job/browserfix` at `2f5a6b9497ab6a395b2fc51bb2dbd1cf2349d782`: **changes required**. Review branch: `job/rev2-browserfix`. Base: `440e19dce23040ac8ebaae88f0469b6535b1afcb`. Review head: `f56d248146226fe55f9d96a43472cd8854e7b663`. Committed files: `review-browserfix.md` (full review) and `audit-findings.md` (evidence log). No product code changed. 1. **P1 — copied push tasks survive revocation (#759).** `crates/plugins/notifications/src/store.rs:701` checks authority before it returns owned tasks. `crates/plugins/notifications/src/push.rs:120` and `:157` send those tasks without a new authority check after endpoint validation. Revocation can delete the subscription while the task waits, but the copied private title and body can still be sent. The worker check limits display; no cross-User display from this race is proved. Also, `store.rs:782` deletes by User and Installation only, so an old failed send can delete a new same-User binding. **Fix:** bind dispatch and result writes to the exact session and subscription generation; cancel queued sends and check authority at the transport boundary. Use an opaque wake-up and authenticated content read for the unavoidable network race. **Test:** hold a fake transport, revoke, then release; assert no private delivery. Rebind and fail the old task; assert that the new binding and another live Installation remain active. 2. **P1 — remote revoke leaves shown private notices (#759).** `apps/web/src/service-worker.ts:67` closes old notices only after a later failed push check; `:138` requires a local session-end message. Neither happens on remote revoke or natural expiry while the app is closed. Server cleanup stops future pushes. At `apps/web/src/lib/userStorage.ts:223`, a fresh sign-in document without a previous User hint can sign in B without session-end cleanup. A's old system notice can remain with B's push off. **Fix:** reconcile shown notices at sign-in and worker wake-up; add a revoke cleanup signal where possible, and use safe system text when offline or expiry prevents cleanup. **Test:** show A's notice, close app windows, revoke or expire A, then sign in B with push off. Assert that A's notice is gone without a new private push, and keep a second live Installation working. 3. **P2 — busy proxy slots leave valid images missing (#766/#726).** `crates/calternal-server/src/image_proxy.rs:51` shares eight immediate-admission slots across all Users. Overflow gets 429. `apps/web/src/lib/notes/NoteImageView.svelte:49` makes that a permanent missing-image state with no retry. **Fix:** retain the resource bounds; add bounded fair admission and retry temporary failures in the shared loader, without external browser fallback. **Test:** hold eight benign image reads, request one more, release the held reads, and assert that the extra image appears without reopening the Note. Include two Users. This is a functional admission failure, not a SLOW-only finding. The thumbnail no-store change, v=2 URL migration, shared outbound resolver and Mail cache epoch cleanup are consistent with their issue requirements by code trace. No unrelated weakened test assertion was found in the relevant history. The thumbnail immutable/304 and Notes direct-origin expectations change as #765 and #766 require. The Notes browser test fulfills the proxy response in Playwright; it does not test successful server transport. Duplicate searches found existing owners #759, #766 and #726. All findings are in this branch, so no separate issue was created. Keep these issues open. Validation: LIGHT rules forbid builds, tests, servers, browsers and performance runs. There is no Rust or web gate output. `git diff --check` exited 0 with no output. The requested fetch and merge of `origin/dev` completed; merge output verbatim: ```text Already up to date. ``` Known gaps: static review only; runtime, transport and OS notification checks remain for a build job. UX gaps closed: none in this read-only job. UX gaps left: F2 and F3. Decisions: review only the fixed target SHA and its browser-fix commits, with related final code; exclude the author's report and uncommitted work. DESIGN §58 at this SHA is agent discovery; use §18 for interactive budgets and §§21, 54 and 57 for authority and cleanup. No product design decision was made.
Author
Owner

Start: job/browserfix at 2f5a6b9497. Read the contract, DESIGN session authority rules, #759 and the independent review. Fix F1/F2/F3 before verification. Existing untracked viewSnapshots files are left untouched.
Fetched origin/job/mailhtml-726 at 76a79589e3. Mail uses crates/plugins/mail/src/remote.rs, a separate prefetch transport with raster decoding. The shared transport must retain Mail's cache and decode policy while centralizing network limits. browserfix owns the shared transport extraction; Mail must consume that module before the two branches merge.
The latest verification policy takes precedence: focused tests and screenshots in this job; full suites, matrices and performance measurements are for the merge round.

Start: job/browserfix at 2f5a6b9497ab6a395b2fc51bb2dbd1cf2349d782. Read the contract, DESIGN session authority rules, #759 and the independent review. Fix F1/F2/F3 before verification. Existing untracked viewSnapshots files are left untouched. Fetched origin/job/mailhtml-726 at 76a79589e338d17dfe3f4c503b8a6e7c8b43e075. Mail uses crates/plugins/mail/src/remote.rs, a separate prefetch transport with raster decoding. The shared transport must retain Mail's cache and decode policy while centralizing network limits. browserfix owns the shared transport extraction; Mail must consume that module before the two branches merge. The latest verification policy takes precedence: focused tests and screenshots in this job; full suites, matrices and performance measurements are for the merge round.
Author
Owner

F1: tasks now retain only opaque inbox/session identities, never title/body/link. A fresh generation fences every receipt, retry and expired-endpoint deletion. Dispatch rechecks authority after endpoint validation and encryption, immediately before polling transport. Added tests for held transport revoke, rebound tasks and second live Installation.
F2: committed 31abd483c. System notices ignore legacy private payload fields and show generic text. Sign-in closes notices even with no previous User hint and push off. Worker activation/push/click reconcile exact sessions; sign-in signals reconciliation. Focused worker tests: 4 passed. Focused push/User-storage tests: 14 passed. Web check: svelte-check found 0 errors and 0 warnings.
F3: extracted canonical transport into calternal-plugin::raster_transport. browserfix owns this module. Shared limits: 5 MiB, 3 redirects, 8 active reads, 64 total admitted requests, bounded FIFO wait and 10-second transport deadline. Notes retains native lazy loading and retries temporary 429/503 on this Instance. Focused recovery tests passed for two concurrent loads, permanent failure and abort.
Mail branch 76a79589e does not use the old server proxy: it has separate sync-time transport plus decoding/caching. No unrelated Mail UI branch is merged. Merge order: browserfix first, then mailhtml-726 with its fetch helper switched to calternal_plugin::raster_transport::{image_url,fetch_image}. Keep Mail's still_image decode and account cache limits; re-export tracking_host from the shared module. A concrete adaptation patch is prepared in artifacts/mail-shared-transport.patch for that branch. Both branches must use this module before the combined merge is accepted. This cross-branch integration remains a merge-round requirement; Mail is not falsely reported as sharing the new transport yet.
Decision: generic system text opens the authenticated Notifications inbox. It never keeps an Event, Log or Note title/link in the operating system after expiry, because no cleanup signal can run while an Installation is offline. Private content remains in the authenticated inbox.

F1: tasks now retain only opaque inbox/session identities, never title/body/link. A fresh generation fences every receipt, retry and expired-endpoint deletion. Dispatch rechecks authority after endpoint validation and encryption, immediately before polling transport. Added tests for held transport revoke, rebound tasks and second live Installation. F2: committed 31abd483c. System notices ignore legacy private payload fields and show generic text. Sign-in closes notices even with no previous User hint and push off. Worker activation/push/click reconcile exact sessions; sign-in signals reconciliation. Focused worker tests: 4 passed. Focused push/User-storage tests: 14 passed. Web check: svelte-check found 0 errors and 0 warnings. F3: extracted canonical transport into calternal-plugin::raster_transport. browserfix owns this module. Shared limits: 5 MiB, 3 redirects, 8 active reads, 64 total admitted requests, bounded FIFO wait and 10-second transport deadline. Notes retains native lazy loading and retries temporary 429/503 on this Instance. Focused recovery tests passed for two concurrent loads, permanent failure and abort. Mail branch 76a79589e does not use the old server proxy: it has separate sync-time transport plus decoding/caching. No unrelated Mail UI branch is merged. Merge order: browserfix first, then mailhtml-726 with its fetch helper switched to calternal_plugin::raster_transport::{image_url,fetch_image}. Keep Mail's still_image decode and account cache limits; re-export tracking_host from the shared module. A concrete adaptation patch is prepared in artifacts/mail-shared-transport.patch for that branch. Both branches must use this module before the combined merge is accepted. This cross-branch integration remains a merge-round requirement; Mail is not falsely reported as sharing the new transport yet. Decision: generic system text opens the authenticated Notifications inbox. It never keeps an Event, Log or Note title/link in the operating system after expiry, because no cleanup signal can run while an Installation is offline. Private content remains in the authenticated inbox.
Author
Owner

F1: tasks now retain only opaque inbox/session identities, never title/body/link. A fresh generation fences every receipt, retry and expired-endpoint deletion. Dispatch rechecks authority after endpoint validation and encryption, immediately before polling transport. Added tests for held transport revoke, rebound tasks and second live Installation.
F2: committed 31abd483c. System notices ignore legacy private payload fields and show generic text. Sign-in closes notices even with no previous User hint and push off. Worker activation/push/click reconcile exact sessions; sign-in signals reconciliation. Focused worker tests: 4 passed. Focused push/User-storage tests: 14 passed. Web check: svelte-check found 0 errors and 0 warnings.
F3: extracted canonical transport into calternal-plugin::raster_transport. browserfix owns this module. Shared limits: 5 MiB, 3 redirects, 8 active reads, 64 total admitted requests, bounded FIFO wait and 10-second transport deadline. Notes retains native lazy loading and retries temporary 429/503 on this Instance. Focused recovery tests passed for two concurrent loads, permanent failure and abort.
Mail branch 76a79589e does not use the old server proxy: it has separate sync-time transport plus decoding/caching. No unrelated Mail UI branch is merged. Merge order: browserfix first, then mailhtml-726 with its fetch helper switched to calternal_plugin::raster_transport::{image_url,fetch_image}. Keep Mail's still_image decode and account cache limits; re-export tracking_host from the shared module. A concrete adaptation patch is prepared in artifacts/mail-shared-transport.patch for that branch. Both branches must use this module before the combined merge is accepted. This cross-branch integration remains a merge-round requirement; Mail is not falsely reported as sharing the new transport yet.
Decision: generic system text opens the authenticated Notifications inbox. It never keeps an Event, Log or Note title/link in the operating system after expiry, because no cleanup signal can run while an Installation is offline. Private content remains in the authenticated inbox.

F1: tasks now retain only opaque inbox/session identities, never title/body/link. A fresh generation fences every receipt, retry and expired-endpoint deletion. Dispatch rechecks authority after endpoint validation and encryption, immediately before polling transport. Added tests for held transport revoke, rebound tasks and second live Installation. F2: committed 31abd483c. System notices ignore legacy private payload fields and show generic text. Sign-in closes notices even with no previous User hint and push off. Worker activation/push/click reconcile exact sessions; sign-in signals reconciliation. Focused worker tests: 4 passed. Focused push/User-storage tests: 14 passed. Web check: svelte-check found 0 errors and 0 warnings. F3: extracted canonical transport into calternal-plugin::raster_transport. browserfix owns this module. Shared limits: 5 MiB, 3 redirects, 8 active reads, 64 total admitted requests, bounded FIFO wait and 10-second transport deadline. Notes retains native lazy loading and retries temporary 429/503 on this Instance. Focused recovery tests passed for two concurrent loads, permanent failure and abort. Mail branch 76a79589e does not use the old server proxy: it has separate sync-time transport plus decoding/caching. No unrelated Mail UI branch is merged. Merge order: browserfix first, then mailhtml-726 with its fetch helper switched to calternal_plugin::raster_transport::{image_url,fetch_image}. Keep Mail's still_image decode and account cache limits; re-export tracking_host from the shared module. A concrete adaptation patch is prepared in artifacts/mail-shared-transport.patch for that branch. Both branches must use this module before the combined merge is accepted. This cross-branch integration remains a merge-round requirement; Mail is not falsely reported as sharing the new transport yet. Decision: generic system text opens the authenticated Notifications inbox. It never keeps an Event, Log or Note title/link in the operating system after expiry, because no cleanup signal can run while an Installation is offline. Private content remains in the authenticated inbox.
Author
Owner

Shared-module handoff: job/browserfix owns calternal-plugin::raster_transport. Notes now calls it through the authenticated server route. The Mail branch must call its public image_url and fetch_image functions at prefetch, with Mail's still_image conversion and account cache policy kept outside the transport.

Concrete Mail adaptation patch for 76a79589e. Merge browserfix first. Apply the patch to mailhtml-726 before accepting the combined merge, and run Mail's focused remote tests and per-crate gates. Its previous local network helper remains test-only; production has one shared network boundary. This patch is attached, not committed as a review artifact and not silently applied in another worktree.

Current verified web result, verbatim:

 Test Files  6 passed (6)
      Tests  40 passed (40)
svelte-check found 0 errors and 0 warnings

The same worker regression against the old entry at 2f5a6b949 gives:

 Test Files  1 failed (1)
      Tests  3 failed | 5 passed (8)

The five original assertions stay unchanged; the three new cases fail on old behavior. Rust is still compiling the first cold dependency graph. No Rust test or Clippy pass is claimed yet.

Shared-module handoff: `job/browserfix` owns `calternal-plugin::raster_transport`. Notes now calls it through the authenticated server route. The Mail branch must call its public `image_url` and `fetch_image` functions at prefetch, with Mail's `still_image` conversion and account cache policy kept outside the transport. [Concrete Mail adaptation patch for 76a79589e](https://git.kayg.org/attachments/1c936b05-b036-4664-aae4-94bc83479abc). Merge browserfix first. Apply the patch to mailhtml-726 before accepting the combined merge, and run Mail's focused remote tests and per-crate gates. Its previous local network helper remains test-only; production has one shared network boundary. This patch is attached, not committed as a review artifact and not silently applied in another worktree. Current verified web result, verbatim: ``` Test Files 6 passed (6) Tests 40 passed (40) ``` ``` svelte-check found 0 errors and 0 warnings ``` The same worker regression against the old entry at 2f5a6b949 gives: ``` Test Files 1 failed (1) Tests 3 failed | 5 passed (8) ``` The five original assertions stay unchanged; the three new cases fail on old behavior. Rust is still compiling the first cold dependency graph. No Rust test or Clippy pass is claimed yet.
Author
Owner

Rust verification hit an environment failure after the first cold dependency build. The shared sccache daemon reports, verbatim:

sccache: encountered fatal error
sccache: error: Failed to create temp dir
sccache: caused by: No such file or directory (os error 2) at path "/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache0e0GCU"

This job's TMPDIR exists; the daemon retained a different job's removed directory. Bypass the wrapper with RUSTC_WRAPPER= in this job only, retain CARGO_BUILD_JOBS=4 and the preset CARGO_TARGET_DIR, and rerun the per-crate checks sequentially. No other worktree or shared daemon is changed. No source assertion is weakened.

Rust verification hit an environment failure after the first cold dependency build. The shared sccache daemon reports, verbatim: ``` sccache: encountered fatal error sccache: error: Failed to create temp dir sccache: caused by: No such file or directory (os error 2) at path "/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache0e0GCU" ``` This job's TMPDIR exists; the daemon retained a different job's removed directory. Bypass the wrapper with `RUSTC_WRAPPER=` in this job only, retain `CARGO_BUILD_JOBS=4` and the preset CARGO_TARGET_DIR, and rerun the per-crate checks sequentially. No other worktree or shared daemon is changed. No source assertion is weakened.
Author
Owner

Notes recovery now uses the existing authenticated fetchSession helper. Before this fix, a retry that received 401 did not dispatch the shared session-expiry event; the mounted app could retain ended-session state. The focused regression fails against the old raw-fetch helper (expected one expiry callback, got zero) and passes with the fix. Commit 3d6fccfa2. No existing test expectation changed.

Verbatim focused result:

 Test Files  1 passed (1)
      Tests  4 passed (4)

Verbatim web check:

svelte-check found 0 errors and 0 warnings

Production build completed with exit 0:

  Wrote site to "build"
  ✔ done

The formatted Mail transport adaptation, including updated module comments, is now attached at https://git.kayg.org/attachments/a8619268-ee74-40de-a28b-6d52702edc97 (supersedes the earlier patch attachment). browserfix still owns the shared module; Mail integration remains required before the combined merge is accepted.

Notes recovery now uses the existing authenticated fetchSession helper. Before this fix, a retry that received 401 did not dispatch the shared session-expiry event; the mounted app could retain ended-session state. The focused regression fails against the old raw-fetch helper (expected one expiry callback, got zero) and passes with the fix. Commit 3d6fccfa2. No existing test expectation changed. Verbatim focused result: ```text Test Files 1 passed (1) Tests 4 passed (4) ``` Verbatim web check: ```text svelte-check found 0 errors and 0 warnings ``` Production build completed with exit 0: ```text Wrote site to "build" ✔ done ``` The formatted Mail transport adaptation, including updated module comments, is now attached at https://git.kayg.org/attachments/a8619268-ee74-40de-a28b-6d52702edc97 (supersedes the earlier patch attachment). browserfix still owns the shared module; Mail integration remains required before the combined merge is accepted.
Author
Owner

UX completion finding: an opaque system notice should still open its item directly. The initial generic-inbox fallback would add a click. The worker now reads GET /api/v1/notifications/inbox/{id} only on click, rechecks the exact session after that read, and opens the returned local deep link. The operating system still retains only opaque identities and generic text. The existing inbox link validator is extracted and reused; no duplicate validation is added. The new item read requires normal data authority, a UUID and the owning User in the indexed lookup. The store regression checks owner success, other-User absence and missing IDs. The focused browser probe checks malformed/anonymous/absent-item HTTP responses.

This supersedes the earlier decision that every generic notice opens the inbox. The inbox remains the fallback for a missing or malformed destination.

Focused worker/model output, verbatim:

 Test Files  2 passed (2)
      Tests  21 passed (21)

Notification Clippy output, verbatim:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 17s

The first attempt exposed an unused HeaderMap import left by the earlier thumbnail cache change; it is removed. The Files crate now also gets its own gates. Notification tests are compiling; production browser evidence and the remaining Rust gates are still pending. The performance profile now includes the item lookup. Measurement stays with the merge round under the latest verification policy.

UX completion finding: an opaque system notice should still open its item directly. The initial generic-inbox fallback would add a click. The worker now reads GET /api/v1/notifications/inbox/{id} only on click, rechecks the exact session after that read, and opens the returned local deep link. The operating system still retains only opaque identities and generic text. The existing inbox link validator is extracted and reused; no duplicate validation is added. The new item read requires normal data authority, a UUID and the owning User in the indexed lookup. The store regression checks owner success, other-User absence and missing IDs. The focused browser probe checks malformed/anonymous/absent-item HTTP responses. This supersedes the earlier decision that every generic notice opens the inbox. The inbox remains the fallback for a missing or malformed destination. Focused worker/model output, verbatim: ```text Test Files 2 passed (2) Tests 21 passed (21) ``` Notification Clippy output, verbatim: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 17s ``` The first attempt exposed an unused HeaderMap import left by the earlier thumbnail cache change; it is removed. The Files crate now also gets its own gates. Notification tests are compiling; production browser evidence and the remaining Rust gates are still pending. The performance profile now includes the item lookup. Measurement stays with the merge round under the latest verification policy.
Author
Owner

Checkpoint 07928c205 preserves the direct authenticated notification-click slice. Notification Clippy, the 21 focused worker/model tests, web check and the production build pass. Rust runtime tests and production browser checks are still pending; this is not a merge-ready claim.

The notification test linker is blocked on shared disk I/O. Evidence at 22:08 UTC: the owned rustc test process waits for its owned rust-lld child, whose process state is D. No test result is available yet. The gate sequence stays serial with CARGO_BUILD_JOBS=4 and two test threads; no rebuild or host-quiet loop is started. Files and server gates follow, then the focused production browser run.

Checkpoint 07928c205 preserves the direct authenticated notification-click slice. Notification Clippy, the 21 focused worker/model tests, web check and the production build pass. Rust runtime tests and production browser checks are still pending; this is not a merge-ready claim. The notification test linker is blocked on shared disk I/O. Evidence at 22:08 UTC: the owned rustc test process waits for its owned rust-lld child, whose process state is D. No test result is available yet. The gate sequence stays serial with CARGO_BUILD_JOBS=4 and two test threads; no rebuild or host-quiet loop is started. Files and server gates follow, then the focused production browser run.
Author
Owner

Notification runtime tests completed with 28 passes and one fixture failure:

called `Result::unwrap()` on an `Err` value: Database(SqliteError { code: 1, message: "table notification_subscriptions has no column named generation" })

The failing block_reminder_migration_preserves_the_inbox_and_push_outbox test builds a legacy schema, then called the current subscribe writer. That writer now requires migration 0006. The fixture now inserts the same legacy subscription columns directly before applying the current migrations. All original preservation assertions stay unchanged; adding 0006 to the legacy setup would hide the upgrade case and is not done. The new copied-task, rebound, held-transport revoke/expiry, endpoint replacement and individual-item User-boundary tests passed. Notification gates are re-running after this fixture-only fix; server and Files gates follow serially.

Notification runtime tests completed with 28 passes and one fixture failure: ```text called `Result::unwrap()` on an `Err` value: Database(SqliteError { code: 1, message: "table notification_subscriptions has no column named generation" }) ``` The failing block_reminder_migration_preserves_the_inbox_and_push_outbox test builds a legacy schema, then called the current subscribe writer. That writer now requires migration 0006. The fixture now inserts the same legacy subscription columns directly before applying the current migrations. All original preservation assertions stay unchanged; adding 0006 to the legacy setup would hide the upgrade case and is not done. The new copied-task, rebound, held-transport revoke/expiry, endpoint replacement and individual-item User-boundary tests passed. Notification gates are re-running after this fixture-only fix; server and Files gates follow serially.
Author
Owner

Commit dd859ae2e fixes a click continuation after async window activation. Before the fix, a held focus request could finish after local cleanup or remote revoke, then post the previous destination. The worker now checks the epoch and exact server session after enumeration/focus, before both in-window navigation and new-window opening. A successful mark-read sends the existing inbox refresh event to every app window so all read badges update.

Four new cases fail against 07928c205 and pass with the fix. Original expectations stay unchanged. Verbatim focused output:

 Test Files  1 passed (1)
      Tests  15 passed (15)

Old-source proof, verbatim:

 Test Files  1 failed (1)
      Tests  4 failed | 11 passed (15)

Web check, verbatim:

svelte-check found 0 errors and 0 warnings

Production build completed with exit 0:

  Wrote site to "build"
  ✔ done

Rust notification tests are still relinking after the legacy fixture fix. The production server, browser captures and remaining per-crate gates are pending.

Commit dd859ae2e fixes a click continuation after async window activation. Before the fix, a held focus request could finish after local cleanup or remote revoke, then post the previous destination. The worker now checks the epoch and exact server session after enumeration/focus, before both in-window navigation and new-window opening. A successful mark-read sends the existing inbox refresh event to every app window so all read badges update. Four new cases fail against 07928c205 and pass with the fix. Original expectations stay unchanged. Verbatim focused output: ```text Test Files 1 passed (1) Tests 15 passed (15) ``` Old-source proof, verbatim: ```text Test Files 1 failed (1) Tests 4 failed | 11 passed (15) ``` Web check, verbatim: ```text svelte-check found 0 errors and 0 warnings ``` Production build completed with exit 0: ```text Wrote site to "build" ✔ done ``` Rust notification tests are still relinking after the legacy fixture fix. The production server, browser captures and remaining per-crate gates are pending.
Author
Owner

Notification gates pass after the fixture fix (c3112a9df). Verbatim runtime result:

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

Verbatim Clippy completion:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 02s

The consolidated focused frontend run passed five files and 38 tests, but its userStorage.svelte.test.ts worker failed to start due to a worker-response timeout. There was no assertion failure. That one file passed on a focused retry:

 Test Files  1 passed (1)
      Tests  10 passed (10)

This gives completed focused coverage for all six files, without rerunning the full suite or changing an expectation. The extra Notes-only check also passed 10 tests. The production server is compiling now. Browser captures, Files gates and server gates are pending.

Notification gates pass after the fixture fix (c3112a9df). Verbatim runtime result: ```text test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.72s ``` Verbatim Clippy completion: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 02s ``` The consolidated focused frontend run passed five files and 38 tests, but its userStorage.svelte.test.ts worker failed to start due to a worker-response timeout. There was no assertion failure. That one file passed on a focused retry: ```text Test Files 1 passed (1) Tests 10 passed (10) ``` This gives completed focused coverage for all six files, without rerunning the full suite or changing an expectation. The extra Notes-only check also passed 10 tests. The production server is compiling now. Browser captures, Files gates and server gates are pending.
Author
Owner

#759 browserfix round — review fixes committed, integration and verification incomplete. Do not merge yet.

Head: 58ffb09648 on job/browserfix. Required origin/dev merge: 06abef84a (origin/dev c4faf184df). No job push, deploy or other merge was run.

Built:

  • F1: copied push tasks carry opaque identities. Dispatch checks the exact live session and subscription generation after endpoint validation. Receipts, retries and expired-endpoint deletion use the same generation. Tests cover revoke, both expiry clocks, rebound subscriptions, endpoint replacement and a second live Installation.
  • F2: system notices retain generic text and opaque identities. Worker wake-up and fresh sign-in close old notices, including a document with no previous User hint and push off. A click reads the private destination through authenticated GET /api/v1/notifications/inbox/{id}; it rechecks authority after window enumeration/focus. Every open app window refreshes its read badge.
  • F3: Notes uses calternal-plugin::raster_transport: 8 active reads, 64 total admitted requests, FIFO admission, a 10-second total deadline, 3 redirects, 5 MiB and raster type checks. It checks and pins public addresses at every hop. Notes retries temporary local failures, cancels at session end and revokes object URLs. A 401 uses the existing shared session cleanup.
  • Corrected the Mail shell-cache comment and removed an obsolete thumbnail HeaderMap import. Fixed legacy migration test setup without changing its preservation assertions.
  • Added the focused production browser probe and extended the performance profile to a 16-image burst and an indexed notification lookup. The probe checks actual image recovery, item-read auth guards and fresh B notice cleanup, then captures Notes/Mail at 390/820/1440, light/dark, with macOS emulation. It is committed but NOT executed yet.

Files:

  • crates/calternal-plugin/{Cargo.toml,src/lib.rs,src/raster_transport.rs}, Cargo.lock; crates/calternal-server/src/image_proxy.rs.
  • crates/plugins/notifications/src/{push,store,routes}.rs and migrations/0006_push_generation.sql.
  • apps/web/src/service-worker.ts; lib/notifications/{push.ts,links.ts,model.ts,serviceWorker.test.ts}; lib/userStorage.ts and userStorage.svelte.test.ts.
  • apps/web/src/lib/{images.ts,images.test.ts,notes/NoteImageView.svelte,mail/readerCache.ts}; crates/plugins/files/src/thumbnails.rs.
  • apps/web/e2e/browserfix-759.mjs; bench/browserfix-image-proxy.mjs.
  • docs/DESIGN.md came from the required dev merge. It was not authored in this round. The two pre-existing untracked viewSnapshots files were preserved.

Verified gates (output excerpts verbatim):
cargo fmt --check: exit 0, no output.
cargo clippy -p calternal-plugin --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 44s

cargo test -p calternal-plugin:

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

cargo clippy -p calternal-plugin-notifications --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 02s

cargo test -p calternal-plugin-notifications -- --test-threads=2:

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

Both crate doc-test phases passed with zero tests.
bun run check:

svelte-check found 0 errors and 0 warnings

Focused Vitest: the consolidated run passed five files/38 tests; its userStorage worker failed to start (worker-response timeout, no assertion failure). That file passed alone. All six files have completed focused coverage:

 Test Files  5 passed (5)
      Tests  38 passed (38)
 Test Files  1 passed (1)
      Tests  10 passed (10)

The worker suite alone passed 15 tests. Its four new window-lifecycle cases fail against 07928c205 (11 older cases pass). Against 2f5a6b949, five privacy/direct-click cases fail and six pass. The new image-401 case also fails on the old raw-fetch helper. No existing assertion was weakened.
Production web build, exit 0:

  Wrote site to "build"
  ✔ done

node --check apps/web/e2e/browserfix-759.mjs and the benchmark script passed. These syntax checks do not prove browser behavior.

Known gaps / merge blockers:

  • The shared host took long periods in Cargo package-cache waits and disk-I/O-blocked linkers. The server reached linking, but rust-lld remained in an uninterruptible disk read (folio_wait_bit_common). At the approximately four-hour job time box, only this worktree's Cargo/compiler/linker processes were terminated. The pipeline exited 143. Files and server Clippy/tests did not start.
  • No production browser run, screenshots or cap-height alignment review is claimed. No benchmark numbers are claimed.
  • Regenerate contracts/openapi.json and packages/api-client/src/generated.ts from the built server before final API gates. The new GET and 503 declaration still need generated artifacts.
  • Mail on dev does not yet contain mailhtml-726's image-prefetch pipeline. browserfix owns the shared module. Merge browserfix first, then make mailhtml-726 consume it before accepting the combined merge. Final adaptation patch for 76a79589e. The patch keeps Mail's still-image decoding, dimension/tracking-pixel rules and account cache limits; its old network helper is test-only. This patch is not compiled against the Mail branch yet. Earlier patch attachments are superseded.

Decisions not specified in DESIGN:

  • Generic system text avoids private remnants while an Installation is offline. On click, the existing item resource gets a small authenticated GET so the User reaches the item directly. Missing/malformed destinations fall back to the inbox.
  • Bounded FIFO admission is 64 total/8 active; temporary 429/503 recovery gets six attempts with capped backoff. No response is persisted and no browser request falls back to the third-party origin.
  • Keep Mail's decode/cache policy outside the one shared network boundary. Do not merge its unrelated UI/cache feature branch into this worktree.

UX gaps closed in code and focused tests: temporary image refusals recover; image-retry 401 ends the session; old system notices close before fresh B sign-in; offline notices hold no private text/link; clicks open the item directly; async window activation cannot continue after session cleanup; read badges refresh in all windows.
UX gaps left: production confirmation, all required screenshots and seeded Mail cache evidence. Mail integration is still pending.

For the merge round / exact remaining commands:

  1. Use CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR="$PWD/target/tmp" and the preset CARGO_TARGET_DIR. RUSTC_WRAPPER= bypasses the shared cache daemon's stale deleted TMPDIR observed in this job. Run cargo build -p calternal-server, then "$CARGO_TARGET_DIR/debug/calternal-server" openapi and cd packages/api-client && bun run generate. This must bring the checked-in contract and client onto the new GET/503 declaration.
  2. cargo clippy -p calternal-plugin-files --all-targets -- -D warnings; cargo test -p calternal-plugin-files -- --test-threads=2; cargo clippy -p calternal-server --all-targets -- -D warnings; cargo test -p calternal-server -- --test-threads=2. These are unfinished required per-crate checks, not passes.
  3. Rebuild web with cd apps/web && bun run build. CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/browserfix-759.mjs must prove actual remote raster recovery, malformed/anonymous/missing-item responses and closed-window A notice removal before B with push off. Attach its 12 macOS screenshot files; check icon cap alignment. CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs must prove the full two-User cache cleanup, 401 and expired-document boot, including seeded Mail caches.
  4. Under the latest verification policy, the combined branch runs cd apps/web && bun run test --maxWorkers=2 and tests/adversarial/run-split.sh once. The latter must cover XUser/Authz/robustness and direct private/link-local image destinations. Extend its controlled public fixture coverage for redirects to those addresses, oversized raster responses and non-image bodies; the current direct-address/refused-burst section alone is insufficient.
  5. Apply the final Mail adaptation after browserfix. Run Mail's focused remote tests and cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings / cargo test -p calternal-plugin-mail. Verify Notes and Mail both use the shared module.
  6. Perf is deferred under the latest policy (this is a security/browser job). On the perf VM only, hold flock /root/perf.lock, record load inside the lock and use the shared release binary for bun bench/browserfix-image-proxy.mjs --runs 20 --json artifacts/browserfix-image-proxy.json. Compare with docs/perf/baseline.json. The default notification metric is explicitly an absent-item lookup; add the populated reminder fixture for its successful-click case. No release build, deploy, Mac interop or full matrix was run in this job.

The touched module/function comments were re-read before this report. Review artifacts remain ignored; none were committed. Cleanup output is recorded separately below.

Cleanup: cargo clean is running against the preset browserfix target only. The cleanup command then deletes apps/web/build and apps/web/.svelte-kit/output. It does not touch the pre-existing untracked files or ignored review artifacts. Completion will be added in a follow-up comment.

#759 browserfix round — review fixes committed, integration and verification incomplete. Do not merge yet. Head: 58ffb096486ff956ab0a8322dac7c76800042944 on job/browserfix. Required origin/dev merge: 06abef84a (origin/dev c4faf184df726a9375ae0c13bdfb6018ac2cf57e). No job push, deploy or other merge was run. Built: - F1: copied push tasks carry opaque identities. Dispatch checks the exact live session and subscription generation after endpoint validation. Receipts, retries and expired-endpoint deletion use the same generation. Tests cover revoke, both expiry clocks, rebound subscriptions, endpoint replacement and a second live Installation. - F2: system notices retain generic text and opaque identities. Worker wake-up and fresh sign-in close old notices, including a document with no previous User hint and push off. A click reads the private destination through authenticated GET /api/v1/notifications/inbox/{id}; it rechecks authority after window enumeration/focus. Every open app window refreshes its read badge. - F3: Notes uses calternal-plugin::raster_transport: 8 active reads, 64 total admitted requests, FIFO admission, a 10-second total deadline, 3 redirects, 5 MiB and raster type checks. It checks and pins public addresses at every hop. Notes retries temporary local failures, cancels at session end and revokes object URLs. A 401 uses the existing shared session cleanup. - Corrected the Mail shell-cache comment and removed an obsolete thumbnail HeaderMap import. Fixed legacy migration test setup without changing its preservation assertions. - Added the focused production browser probe and extended the performance profile to a 16-image burst and an indexed notification lookup. The probe checks actual image recovery, item-read auth guards and fresh B notice cleanup, then captures Notes/Mail at 390/820/1440, light/dark, with macOS emulation. It is committed but NOT executed yet. Files: - crates/calternal-plugin/{Cargo.toml,src/lib.rs,src/raster_transport.rs}, Cargo.lock; crates/calternal-server/src/image_proxy.rs. - crates/plugins/notifications/src/{push,store,routes}.rs and migrations/0006_push_generation.sql. - apps/web/src/service-worker.ts; lib/notifications/{push.ts,links.ts,model.ts,serviceWorker.test.ts}; lib/userStorage.ts and userStorage.svelte.test.ts. - apps/web/src/lib/{images.ts,images.test.ts,notes/NoteImageView.svelte,mail/readerCache.ts}; crates/plugins/files/src/thumbnails.rs. - apps/web/e2e/browserfix-759.mjs; bench/browserfix-image-proxy.mjs. - docs/DESIGN.md came from the required dev merge. It was not authored in this round. The two pre-existing untracked viewSnapshots files were preserved. Verified gates (output excerpts verbatim): `cargo fmt --check`: exit 0, no output. `cargo clippy -p calternal-plugin --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 44s ``` `cargo test -p calternal-plugin`: ```text test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 53.98s ``` `cargo clippy -p calternal-plugin-notifications --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 02s ``` `cargo test -p calternal-plugin-notifications -- --test-threads=2`: ```text test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.72s ``` Both crate doc-test phases passed with zero tests. `bun run check`: ```text svelte-check found 0 errors and 0 warnings ``` Focused Vitest: the consolidated run passed five files/38 tests; its userStorage worker failed to start (worker-response timeout, no assertion failure). That file passed alone. All six files have completed focused coverage: ```text Test Files 5 passed (5) Tests 38 passed (38) ``` ```text Test Files 1 passed (1) Tests 10 passed (10) ``` The worker suite alone passed 15 tests. Its four new window-lifecycle cases fail against 07928c205 (11 older cases pass). Against 2f5a6b949, five privacy/direct-click cases fail and six pass. The new image-401 case also fails on the old raw-fetch helper. No existing assertion was weakened. Production web build, exit 0: ```text Wrote site to "build" ✔ done ``` `node --check apps/web/e2e/browserfix-759.mjs` and the benchmark script passed. These syntax checks do not prove browser behavior. Known gaps / merge blockers: - The shared host took long periods in Cargo package-cache waits and disk-I/O-blocked linkers. The server reached linking, but rust-lld remained in an uninterruptible disk read (folio_wait_bit_common). At the approximately four-hour job time box, only this worktree's Cargo/compiler/linker processes were terminated. The pipeline exited 143. Files and server Clippy/tests did not start. - No production browser run, screenshots or cap-height alignment review is claimed. No benchmark numbers are claimed. - Regenerate contracts/openapi.json and packages/api-client/src/generated.ts from the built server before final API gates. The new GET and 503 declaration still need generated artifacts. - Mail on dev does not yet contain mailhtml-726's image-prefetch pipeline. browserfix owns the shared module. Merge browserfix first, then make mailhtml-726 consume it before accepting the combined merge. [Final adaptation patch for 76a79589e](https://git.kayg.org/attachments/15c73f2b-72d5-49b5-aefc-61877c69c889). The patch keeps Mail's still-image decoding, dimension/tracking-pixel rules and account cache limits; its old network helper is test-only. This patch is not compiled against the Mail branch yet. Earlier patch attachments are superseded. Decisions not specified in DESIGN: - Generic system text avoids private remnants while an Installation is offline. On click, the existing item resource gets a small authenticated GET so the User reaches the item directly. Missing/malformed destinations fall back to the inbox. - Bounded FIFO admission is 64 total/8 active; temporary 429/503 recovery gets six attempts with capped backoff. No response is persisted and no browser request falls back to the third-party origin. - Keep Mail's decode/cache policy outside the one shared network boundary. Do not merge its unrelated UI/cache feature branch into this worktree. UX gaps closed in code and focused tests: temporary image refusals recover; image-retry 401 ends the session; old system notices close before fresh B sign-in; offline notices hold no private text/link; clicks open the item directly; async window activation cannot continue after session cleanup; read badges refresh in all windows. UX gaps left: production confirmation, all required screenshots and seeded Mail cache evidence. Mail integration is still pending. For the merge round / exact remaining commands: 1. Use CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR="$PWD/target/tmp" and the preset CARGO_TARGET_DIR. RUSTC_WRAPPER= bypasses the shared cache daemon's stale deleted TMPDIR observed in this job. Run `cargo build -p calternal-server`, then `"$CARGO_TARGET_DIR/debug/calternal-server" openapi` and `cd packages/api-client && bun run generate`. This must bring the checked-in contract and client onto the new GET/503 declaration. 2. `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings`; `cargo test -p calternal-plugin-files -- --test-threads=2`; `cargo clippy -p calternal-server --all-targets -- -D warnings`; `cargo test -p calternal-server -- --test-threads=2`. These are unfinished required per-crate checks, not passes. 3. Rebuild web with `cd apps/web && bun run build`. `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/browserfix-759.mjs` must prove actual remote raster recovery, malformed/anonymous/missing-item responses and closed-window A notice removal before B with push off. Attach its 12 macOS screenshot files; check icon cap alignment. `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs` must prove the full two-User cache cleanup, 401 and expired-document boot, including seeded Mail caches. 4. Under the latest verification policy, the combined branch runs `cd apps/web && bun run test --maxWorkers=2` and `tests/adversarial/run-split.sh` once. The latter must cover XUser/Authz/robustness and direct private/link-local image destinations. Extend its controlled public fixture coverage for redirects to those addresses, oversized raster responses and non-image bodies; the current direct-address/refused-burst section alone is insufficient. 5. Apply the final Mail adaptation after browserfix. Run Mail's focused remote tests and `cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings` / `cargo test -p calternal-plugin-mail`. Verify Notes and Mail both use the shared module. 6. Perf is deferred under the latest policy (this is a security/browser job). On the perf VM only, hold `flock /root/perf.lock`, record load inside the lock and use the shared release binary for `bun bench/browserfix-image-proxy.mjs --runs 20 --json artifacts/browserfix-image-proxy.json`. Compare with docs/perf/baseline.json. The default notification metric is explicitly an absent-item lookup; add the populated reminder fixture for its successful-click case. No release build, deploy, Mac interop or full matrix was run in this job. The touched module/function comments were re-read before this report. Review artifacts remain ignored; none were committed. Cleanup output is recorded separately below. Cleanup: `cargo clean` is running against the preset browserfix target only. The cleanup command then deletes apps/web/build and apps/web/.svelte-kit/output. It does not touch the pre-existing untracked files or ignored review artifacts. Completion will be added in a follow-up comment.
Author
Owner

Cleanup completed, exit 0. Verbatim cargo clean output:

     Removed 14609 files, 6.9GiB total

Deleted apps/web/build and apps/web/.svelte-kit/output. Review artifacts and the two pre-existing untracked viewSnapshots files remain. Head is unchanged: 58ffb09648.

Cleanup completed, exit 0. Verbatim `cargo clean` output: ```text Removed 14609 files, 6.9GiB total ``` Deleted apps/web/build and apps/web/.svelte-kit/output. Review artifacts and the two pre-existing untracked viewSnapshots files remain. Head is unchanged: 58ffb096486ff956ab0a8322dac7c76800042944.
Author
Owner

Resumed #759 on job/browserfix at 58ffb09648. I read CLAUDE.md, CONTEXT.md, DESIGN and the prior report. I will merge origin/dev once, build web first, run the unfinished per-crate gates with OPENSSL_NO_VENDOR=1, regenerate the API contract and run the focused production image/session probe. The existing untracked viewSnapshots files remain untouched. Shared raster signatures remain stable for mailhtml-726. Full suites, adversarial matrices, perf, release/deploy and Mac interop remain for the merge round under the current verification policy.

Resumed #759 on job/browserfix at 58ffb096486ff956ab0a8322dac7c76800042944. I read CLAUDE.md, CONTEXT.md, DESIGN and the prior report. I will merge origin/dev once, build web first, run the unfinished per-crate gates with OPENSSL_NO_VENDOR=1, regenerate the API contract and run the focused production image/session probe. The existing untracked viewSnapshots files remain untouched. Shared raster signatures remain stable for mailhtml-726. Full suites, adversarial matrices, perf, release/deploy and Mac interop remain for the merge round under the current verification policy.
Author
Owner

Integration: merged origin/dev bd11bacb51 into job/browserfix at cc7601e428 without conflicts. origin/dev Notifications migrations stop at 0004, so this branch retains 0005_push_sessions.sql and 0006_push_generation.sql. Production web build completed: Wrote site to "build", ✔ done. The raster_transport module now documents the stable image_url/fetch_image/MAX_BYTES interface and caller-owned decoding, authorization, cancellation and cache policy for mailhtml-726. No runtime change to that interface. Rust gates are compiling with OPENSSL_NO_VENDOR=1; no unfinished gate is claimed as passed.

Integration: merged origin/dev bd11bacb5189d39176e7cd48d5e977f1694321c1 into job/browserfix at cc7601e42869add10c5b011b6d94ead774766abd without conflicts. origin/dev Notifications migrations stop at 0004, so this branch retains 0005_push_sessions.sql and 0006_push_generation.sql. Production web build completed: `Wrote site to "build"`, `✔ done`. The raster_transport module now documents the stable image_url/fetch_image/MAX_BYTES interface and caller-owned decoding, authorization, cancellation and cache policy for mailhtml-726. No runtime change to that interface. Rust gates are compiling with OPENSSL_NO_VENDOR=1; no unfinished gate is claimed as passed.
Author
Owner

Verified after origin/dev integration: cargo fmt --check exited 0 with no output. Notifications Clippy completed:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s

Web check:

svelte-check found 0 errors and 0 warnings

Focused Vitest:

 Test Files  9 passed (9)
      Tests  68 passed (68)

Production web build completed. Server build and remaining per-crate gates are running in sequence. The shared raster interface has only documentation changes; it remains compatible with the final Mail adaptation patch attached in the prior report.

Verified after origin/dev integration: `cargo fmt --check` exited 0 with no output. Notifications Clippy completed: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s ``` Web check: ``` svelte-check found 0 errors and 0 warnings ``` Focused Vitest: ``` Test Files 9 passed (9) Tests 68 passed (68) ``` Production web build completed. Server build and remaining per-crate gates are running in sequence. The shared raster interface has only documentation changes; it remains compatible with the final Mail adaptation patch attached in the prior report.
Author
Owner

The real production image probe reached the actual authenticated raster response and recovered after its temporary 429. The first screenshot failed because the probe passed light as a palette name to setTheme: its diagnostic reported mode=system, palette=mono and storedFamily=light. The probe now uses the existing mode-object API and route reload, with every theme assertion retained. It captured Notes at all six required width/theme combinations, then exposed a notification permission failure while seeding the old-system-notice fixture. The test now scopes its permission grant to the test origin and explicitly checks Notification.permission before using the real worker.

Notifications gates now pass:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.18s

The production server built successfully, and its OpenAPI generator plus openapi-typescript 7.13.0 regenerated the contract and client. The regenerated client passed bun run check:

svelte-check found 0 errors and 0 warnings

The new contract includes GET /api/v1/notifications/inbox/{id}, image-proxy 503, and the private no-store thumbnail descriptions. It also refreshes current origin/dev handler documentation. Shared raster signatures are unchanged.

The real production image probe reached the actual authenticated raster response and recovered after its temporary 429. The first screenshot failed because the probe passed `light` as a palette name to setTheme: its diagnostic reported mode=system, palette=mono and storedFamily=light. The probe now uses the existing mode-object API and route reload, with every theme assertion retained. It captured Notes at all six required width/theme combinations, then exposed a notification permission failure while seeding the old-system-notice fixture. The test now scopes its permission grant to the test origin and explicitly checks Notification.permission before using the real worker. Notifications gates now pass: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s ``` ``` test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.18s ``` The production server built successfully, and its OpenAPI generator plus openapi-typescript 7.13.0 regenerated the contract and client. The regenerated client passed `bun run check`: ``` svelte-check found 0 errors and 0 warnings ``` The new contract includes GET /api/v1/notifications/inbox/{id}, image-proxy 503, and the private no-store thumbnail descriptions. It also refreshes current origin/dev handler documentation. Shared raster signatures are unchanged.
Author
Owner

Two verification-only causes found and fixed, without changing existing expectations:

  • Files Clippy failed at lib.rs:13459 and 13483: HOSTILE_HTML.as_slice() uses unstable str_as_str on pinned Rust 1.98.1. HOSTILE_HTML is already &[u8]. Both assertions now compare that identical byte slice directly. Files Clippy passes after the change; its test gate is running.
  • A small browser diagnostic reports Notification.permission=denied but navigator.permissions.query=granted in Playwright 1.63.0's default headless shell, on both HTTP and HTTPS loopback. With the full chromium channel, both APIs report granted on both origins. The focused production probe now uses that full browser channel and keeps the real worker/notice assertions. No notification API is mocked. See https://playwright.dev/docs/browsers#chromium-new-headless-mode for the channel distinction.

Shared plugin gates pass:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 05s
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.63s
Two verification-only causes found and fixed, without changing existing expectations: - Files Clippy failed at lib.rs:13459 and 13483: `HOSTILE_HTML.as_slice()` uses unstable `str_as_str` on pinned Rust 1.98.1. HOSTILE_HTML is already `&[u8]`. Both assertions now compare that identical byte slice directly. Files Clippy passes after the change; its test gate is running. - A small browser diagnostic reports Notification.permission=denied but navigator.permissions.query=granted in Playwright 1.63.0's default headless shell, on both HTTP and HTTPS loopback. With the full `chromium` channel, both APIs report granted on both origins. The focused production probe now uses that full browser channel and keeps the real worker/notice assertions. No notification API is mocked. See https://playwright.dev/docs/browsers#chromium-new-headless-mode for the channel distinction. Shared plugin gates pass: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 05s ``` ``` test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.63s ```
Author
Owner

Focused production regression passed against this branch's built server and web:

#759 owner registered; checking authenticated routes
#759 real remote raster recovered after temporary admission refusal
#759 app closed and A session ended; starting fresh B sign-in
#759 B with push off has no A system notification
#759 capturing B Mail in light
#759 capturing B Mail in dark
#759 production regression: proxy retry recovery and fresh-document notice cleanup passed; 12 macOS screenshots captured

The test uses full Chromium, the shared HTTPS front, scoped real notification permission, the real service worker and Origin on the closed-window session-delete request. It captures the default Mono/Noir family so B's server-backed signup preference stays consistent. Theme and API expectations were retained. The two-User storage regression is running next, one browser at a time.

Files Clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.37s

Files tests:

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

Server Clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 17s

Server tests are compiling/running. Screenshots remain ignored and will be attached to this issue.

Focused production regression passed against this branch's built server and web: ``` #759 owner registered; checking authenticated routes #759 real remote raster recovered after temporary admission refusal #759 app closed and A session ended; starting fresh B sign-in #759 B with push off has no A system notification #759 capturing B Mail in light #759 capturing B Mail in dark #759 production regression: proxy retry recovery and fresh-document notice cleanup passed; 12 macOS screenshots captured ``` The test uses full Chromium, the shared HTTPS front, scoped real notification permission, the real service worker and Origin on the closed-window session-delete request. It captures the default Mono/Noir family so B's server-backed signup preference stays consistent. Theme and API expectations were retained. The two-User storage regression is running next, one browser at a time. Files Clippy: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.37s ``` Files tests: ``` test result: ok. 158 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 253.46s ``` Server Clippy: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 17s ``` Server tests are compiling/running. Screenshots remain ignored and will be attached to this issue.
Author
Owner

Remaining #555 production check found a setup race, before any session-cleanup assertions: the freshly uploaded picture's v=2 thumbnail returned nine 404 responses while its real thumbnail Job ran, then 200 image/webp at the failure diagnostic. The server Appearance read was 200, savedItemMatches=true, mode=light, palette=mono, and layer kind=image. The original 30-second picture assertion remains unchanged. The test now waits for the real Job's 200 image/webp before assigning the picture and entering the cleanup scenarios, consistent with the Files renderer fixtures; no bytes or app state are mocked. Added failure diagnostics show only readiness booleans, HTTP states and the existing redacted server diagnostics.

Separate nonblocking UX gap: a newly selected background can remain unpainted if its early thumbnail reads returned 404; after bytes become available it waits for a later apply rather than retrying automatically. This is appearance recovery, not cross-User disclosure. I will record it separately and keep the session-cleanup test's prepared picture invariant.

All four Rust crates' default gates now pass. Server tests:

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

All 12 Notes/Mail screenshots are attached to this issue; icon/text cap alignment was checked from 4x offline crops at every width/theme. Visual review remains with the orchestrator.

Remaining #555 production check found a setup race, before any session-cleanup assertions: the freshly uploaded picture's v=2 thumbnail returned nine 404 responses while its real thumbnail Job ran, then 200 image/webp at the failure diagnostic. The server Appearance read was 200, savedItemMatches=true, mode=light, palette=mono, and layer kind=image. The original 30-second picture assertion remains unchanged. The test now waits for the real Job's 200 image/webp before assigning the picture and entering the cleanup scenarios, consistent with the Files renderer fixtures; no bytes or app state are mocked. Added failure diagnostics show only readiness booleans, HTTP states and the existing redacted server diagnostics. Separate nonblocking UX gap: a newly selected background can remain unpainted if its early thumbnail reads returned 404; after bytes become available it waits for a later apply rather than retrying automatically. This is appearance recovery, not cross-User disclosure. I will record it separately and keep the session-cleanup test's prepared picture invariant. All four Rust crates' default gates now pass. Server tests: ``` test result: ok. 165 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 31.61s ``` All 12 Notes/Mail screenshots are attached to this issue; icon/text cap alignment was checked from 4x offline crops at every width/theme. Visual review remains with the orchestrator.
Author
Owner

The remaining production storage regression now passes. The initial failure was a test prerequisite race: selecting the background before the real Files thumbnail Job finished leaves the first decode unsuccessful. The test now waits for a real thumbnail 200 before assigning the picture, with a finite 120-second limit. It keeps every cleanup assertion and uses real bytes. Native probe/render checks also passed; this run does not establish a media-runtime defect. Temporary diagnostics were removed.

Commit: 66d1ef3d37.

#555 signout: A picture and all four browser backends are populated
PASS #555 signout: no A frame, DOM content or storage survives into B
#555 expired-live: A picture and all four browser backends are populated
PASS #555 expired-live: no A frame, DOM content or storage survives into B
#555 expired-document: A picture and all four browser backends are populated
PASS #555 expired-document: no A frame, DOM content or storage survives into B

This includes real revoked-session 401, expired-document boot, first-frame sampling, and seeded localStorage/sessionStorage/IndexedDB/Cache Storage Mail and Money cache fixtures. The separate #759 probe passed earlier. All required per-crate gates are complete. Final report and cleanup follow.

The remaining production storage regression now passes. The initial failure was a test prerequisite race: selecting the background before the real Files thumbnail Job finished leaves the first decode unsuccessful. The test now waits for a real thumbnail 200 before assigning the picture, with a finite 120-second limit. It keeps every cleanup assertion and uses real bytes. Native probe/render checks also passed; this run does not establish a media-runtime defect. Temporary diagnostics were removed. Commit: 66d1ef3d378613bc619591e3396a26632747f0dd. ``` #555 signout: A picture and all four browser backends are populated PASS #555 signout: no A frame, DOM content or storage survives into B #555 expired-live: A picture and all four browser backends are populated PASS #555 expired-live: no A frame, DOM content or storage survives into B #555 expired-document: A picture and all four browser backends are populated PASS #555 expired-document: no A frame, DOM content or storage survives into B ``` This includes real revoked-session 401, expired-document boot, first-frame sampling, and seeded localStorage/sessionStorage/IndexedDB/Cache Storage Mail and Money cache fixtures. The separate #759 probe passed earlier. All required per-crate gates are complete. Final report and cleanup follow.
Author
Owner

READY FOR MERGE: yes

Head: 66d1ef3d378613bc619591e3396a26632747f0dd on job/browserfix.
Integrated origin/dev once: merge cc7601e42869add10c5b011b6d94ead774766abd, upstream bd11bacb5189d39176e7cd48d5e977f1694321c1. No push, deploy, or merge into dev/main was run.

Built / completed

  • Finished integration and the required gates left incomplete at 58ffb0964. The earlier session/subscription-generation guards, authenticated notice destination lookup, fresh-document notice cleanup, shared bounded raster transport and finite browser retry remain intact.
  • Documented the stable shared raster consumer contract for mailhtml-726. image_url(&str) and fetch_image(Url) signatures are unchanged. Mail retains decoding, dimensions, authorization, cancellation and cache policy. The transport controls URL/DNS admission, redirects, public destination pinning, MIME/signature checks, response bounds and queue/deadline limits.
  • Regenerated OpenAPI and the TypeScript client from this branch's built server. The client now includes the authenticated notice GET and image 503 response.
  • Fixed two existing Files fixture comparisons for pinned stable Rust. Fixture bytes and assertions are unchanged.
  • Made the focused production probe use full Chromium with real notification permission, the shared HTTPS fixture, saved Mono/Noir palettes and Origin on the closed-window session-delete request.
  • Made the storage regression prepare its real thumbnail before testing session cleanup. No mocked image bytes, changed expectations or removed cleanup assertions.

Resume files
crates/calternal-plugin/src/raster_transport.rs; crates/plugins/files/src/lib.rs; contracts/openapi.json; packages/api-client/src/generated.ts; apps/web/e2e/browserfix-759.mjs; apps/web/e2e/user-storage-555.mjs.
Atomic resume commits: cc7601e42, 91f4a4d60, 1a85b2eb4, 9400fcc11, 5774773d7, 66d1ef3d3.

Gate output (verbatim)
All Cargo commands used the preset target, CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 OPENSSL_NO_VENDOR=1 TMPDIR=$PWD/target/tmp RUSTC_WRAPPER=. Web was built before the server. No crate/package dependency version was changed. Client generation used the installed pinned openapi-typescript 7.13.0.

cargo fmt --check: exit 0, no output.

cargo clippy -p calternal-plugin-notifications --all-targets -- -D warnings

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s

cargo test -p calternal-plugin-notifications (bounded test threads)

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

cargo clippy -p calternal-plugin --all-targets -- -D warnings

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 05s

cargo test -p calternal-plugin (bounded test threads)

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

cargo clippy -p calternal-plugin-files --all-targets -- -D warnings

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.37s

cargo test -p calternal-plugin-files (bounded test threads)

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

cargo clippy -p calternal-server --all-targets -- -D warnings

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 17s

cargo test -p calternal-server (bounded test threads)

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

cd apps/web && bun run check

svelte-check found 0 errors and 0 warnings

cd apps/web && bunx vitest run src/lib/notifications/push.test.ts src/lib/notifications/serviceWorker.test.ts src/lib/images.test.ts src/lib/mail/readerCache.test.ts src/lib/files/api.test.ts src/lib/files/live.test.ts src/lib/api/revision-cache.test.ts src/lib/userStorage.svelte.test.ts src/lib/notes/notes.test.ts --maxWorkers=2

 Test Files  9 passed (9)
      Tests  68 passed (68)

Focused production verification (this branch's server and production web)
CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/browserfix-759.mjs

#759 owner registered; checking authenticated routes
#759 real remote raster recovered after temporary admission refusal
#759 app closed and A session ended; starting fresh B sign-in
#759 B with push off has no A system notification
#759 capturing B Mail in light
#759 capturing B Mail in dark
#759 production regression: proxy retry recovery and fresh-document notice cleanup passed; 12 macOS screenshots captured

CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs signout
CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs expired-live expired-document
The camera fixture needs the host's libstdc++ in LD_LIBRARY_PATH; the toolchain path used here is recorded in the run log. Output:

#555 signout: A picture and all four browser backends are populated
PASS #555 signout: no A frame, DOM content or storage survives into B
#555 expired-live: A picture and all four browser backends are populated
PASS #555 expired-live: no A frame, DOM content or storage survives into B
#555 expired-document: A picture and all four browser backends are populated
PASS #555 expired-document: no A frame, DOM content or storage survives into B

UX gaps closed

  • Real remote image recovers after temporary admission refusal without an extra User action.
  • A system notice is removed after the app closes and A's session ends, before B signs in with push off.
  • Real A background and seeded localStorage, sessionStorage, IndexedDB and Cache Storage are absent from B after sign-out, real 401 and expired-document boot. Frame sampling checks the transition, not only the final DOM.

UX gaps left / known gaps

  • #1025: a background selected before its renderer Job finishes can miss the first thumbnail and stay unchanged. This separate Background UX gap is filed with production evidence. The storage test now prepares its prerequisite; product retry behavior is unchanged.
  • Mail's consumer integration is still owned by mailhtml-726. Apply the final adaptation patch for 76a79589e after browserfix, then compile/test that branch. Earlier patch attachments are superseded. This worktree does not claim the Mail prefetch feature is integrated.
  • The notification probe uses a real service-worker system notice, not an external push service. Held-transport generation tests cover revoke, expiry, rebound subscriptions, endpoint replacement and another live Installation.
  • Ignored Rust cases remain explicitly ignored: Files' 10,000-file live transfer; server performance and standalone live-app/DAV review cases. The server's separate-process live-app wrapper passed. Full matrices, release/deploy, Mac interop and perf were not run under the job verification policy.

Decisions

  • Keep Mail decode/cache policy outside the stable shared transport. No public raster API signature changed.
  • Use full Chromium and the existing HTTPS fixture for real notification APIs; use the actual default Mono/Noir family for saved-theme captures.
  • Prepare the real asynchronous thumbnail before testing storage isolation. Keep the product readiness gap separate as #1025. No existing assertion or fixture expectation changed.
  • Retain the two pre-existing untracked viewSnapshots files. No changes were made to them.

For the merge round

  1. cd apps/web && bun run test --maxWorkers=2 and the full e2e suite: check the combined branch once.
  2. tests/adversarial/run-split.sh: run XUser/Authz/robustness. Extend controlled public raster fixtures to cover redirects to private/link-local addresses, oversized responses and non-image bodies; direct-address/refused-burst coverage alone does not prove those cases.
  3. After applying the Mail adaptation: Mail's focused remote-image tests, cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings, cargo test -p calternal-plugin-mail -- --test-threads=4; prove Notes and Mail use the shared raster transport.
  4. If the merge round requests deferred perf, only on the perf VM with the shared release binary: flock /root/perf.lock bash -c 'uptime; bun bench/browserfix-image-proxy.mjs --runs 20 --json artifacts/browserfix-image-proxy.json'. Compare docs/perf/baseline.json and add a populated reminder fixture for successful notification lookup. This job made no perf claim.
  5. Release, staging/o2 and Mac interop checks remain the orchestrator's merge-round work.

Review evidence
All 12 #759 screenshots use macOS emulation at 390/820/1440 px in light and dark. Notes/Mail icon cap alignment was inspected using enlarged captured pixels. Visual quality remains for Claude's review. The storage probe adds 24 macOS screenshots of Login, B Mail, B Money and B Appearance. No screenshots or diagnostics were committed.

Doc comments for every authored resume change were re-read. Temporary diagnostic edits were removed. Cleanup completed against only the preset job target:

     Removed 18391 files, 10.2GiB total
Deleted apps/web/build and apps/web/.svelte-kit/output.

Working tree has only the two pre-existing untracked viewSnapshots files. Required job gates and both focused production probes pass. READY FOR MERGE: yes (the combined merge round must still validate Mail integration and its full suites).

READY FOR MERGE: yes Head: `66d1ef3d378613bc619591e3396a26632747f0dd` on `job/browserfix`. Integrated `origin/dev` once: merge `cc7601e42869add10c5b011b6d94ead774766abd`, upstream `bd11bacb5189d39176e7cd48d5e977f1694321c1`. No push, deploy, or merge into dev/main was run. Built / completed - Finished integration and the required gates left incomplete at 58ffb0964. The earlier session/subscription-generation guards, authenticated notice destination lookup, fresh-document notice cleanup, shared bounded raster transport and finite browser retry remain intact. - Documented the stable shared raster consumer contract for mailhtml-726. `image_url(&str)` and `fetch_image(Url)` signatures are unchanged. Mail retains decoding, dimensions, authorization, cancellation and cache policy. The transport controls URL/DNS admission, redirects, public destination pinning, MIME/signature checks, response bounds and queue/deadline limits. - Regenerated OpenAPI and the TypeScript client from this branch's built server. The client now includes the authenticated notice GET and image 503 response. - Fixed two existing Files fixture comparisons for pinned stable Rust. Fixture bytes and assertions are unchanged. - Made the focused production probe use full Chromium with real notification permission, the shared HTTPS fixture, saved Mono/Noir palettes and Origin on the closed-window session-delete request. - Made the storage regression prepare its real thumbnail before testing session cleanup. No mocked image bytes, changed expectations or removed cleanup assertions. Resume files `crates/calternal-plugin/src/raster_transport.rs`; `crates/plugins/files/src/lib.rs`; `contracts/openapi.json`; `packages/api-client/src/generated.ts`; `apps/web/e2e/browserfix-759.mjs`; `apps/web/e2e/user-storage-555.mjs`. Atomic resume commits: `cc7601e42`, `91f4a4d60`, `1a85b2eb4`, `9400fcc11`, `5774773d7`, `66d1ef3d3`. Gate output (verbatim) All Cargo commands used the preset target, `CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 OPENSSL_NO_VENDOR=1 TMPDIR=$PWD/target/tmp RUSTC_WRAPPER=`. Web was built before the server. No crate/package dependency version was changed. Client generation used the installed pinned openapi-typescript 7.13.0. `cargo fmt --check`: exit 0, no output. `cargo clippy -p calternal-plugin-notifications --all-targets -- -D warnings` ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 12s ``` `cargo test -p calternal-plugin-notifications` (bounded test threads) ``` test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.18s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin --all-targets -- -D warnings` ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 05s ``` `cargo test -p calternal-plugin` (bounded test threads) ``` test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.63s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings` ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 28.37s ``` `cargo test -p calternal-plugin-files` (bounded test threads) ``` test result: ok. 158 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 253.46s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings` ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 17s ``` `cargo test -p calternal-server` (bounded test threads) ``` test result: ok. 165 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 31.61s ``` `cd apps/web && bun run check` ``` svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bunx vitest run src/lib/notifications/push.test.ts src/lib/notifications/serviceWorker.test.ts src/lib/images.test.ts src/lib/mail/readerCache.test.ts src/lib/files/api.test.ts src/lib/files/live.test.ts src/lib/api/revision-cache.test.ts src/lib/userStorage.svelte.test.ts src/lib/notes/notes.test.ts --maxWorkers=2` ``` Test Files 9 passed (9) Tests 68 passed (68) ``` Focused production verification (this branch's server and production web) `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/browserfix-759.mjs` ``` #759 owner registered; checking authenticated routes #759 real remote raster recovered after temporary admission refusal #759 app closed and A session ended; starting fresh B sign-in #759 B with push off has no A system notification #759 capturing B Mail in light #759 capturing B Mail in dark #759 production regression: proxy retry recovery and fresh-document notice cleanup passed; 12 macOS screenshots captured ``` `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs signout` `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" bun apps/web/e2e/user-storage-555.mjs expired-live expired-document` The camera fixture needs the host's libstdc++ in LD_LIBRARY_PATH; the toolchain path used here is recorded in the run log. Output: ``` #555 signout: A picture and all four browser backends are populated PASS #555 signout: no A frame, DOM content or storage survives into B #555 expired-live: A picture and all four browser backends are populated PASS #555 expired-live: no A frame, DOM content or storage survives into B #555 expired-document: A picture and all four browser backends are populated PASS #555 expired-document: no A frame, DOM content or storage survives into B ``` UX gaps closed - Real remote image recovers after temporary admission refusal without an extra User action. - A system notice is removed after the app closes and A's session ends, before B signs in with push off. - Real A background and seeded localStorage, sessionStorage, IndexedDB and Cache Storage are absent from B after sign-out, real 401 and expired-document boot. Frame sampling checks the transition, not only the final DOM. UX gaps left / known gaps - [#1025](https://git.kayg.org/kayg/calternal/issues/1025): a background selected before its renderer Job finishes can miss the first thumbnail and stay unchanged. This separate Background UX gap is filed with production evidence. The storage test now prepares its prerequisite; product retry behavior is unchanged. - Mail's consumer integration is still owned by mailhtml-726. Apply the [final adaptation patch for 76a79589e](https://git.kayg.org/attachments/15c73f2b-72d5-49b5-aefc-61877c69c889) after browserfix, then compile/test that branch. Earlier patch attachments are superseded. This worktree does not claim the Mail prefetch feature is integrated. - The notification probe uses a real service-worker system notice, not an external push service. Held-transport generation tests cover revoke, expiry, rebound subscriptions, endpoint replacement and another live Installation. - Ignored Rust cases remain explicitly ignored: Files' 10,000-file live transfer; server performance and standalone live-app/DAV review cases. The server's separate-process live-app wrapper passed. Full matrices, release/deploy, Mac interop and perf were not run under the job verification policy. Decisions - Keep Mail decode/cache policy outside the stable shared transport. No public raster API signature changed. - Use full Chromium and the existing HTTPS fixture for real notification APIs; use the actual default Mono/Noir family for saved-theme captures. - Prepare the real asynchronous thumbnail before testing storage isolation. Keep the product readiness gap separate as #1025. No existing assertion or fixture expectation changed. - Retain the two pre-existing untracked viewSnapshots files. No changes were made to them. For the merge round 1. `cd apps/web && bun run test --maxWorkers=2` and the full e2e suite: check the combined branch once. 2. `tests/adversarial/run-split.sh`: run XUser/Authz/robustness. Extend controlled public raster fixtures to cover redirects to private/link-local addresses, oversized responses and non-image bodies; direct-address/refused-burst coverage alone does not prove those cases. 3. After applying the Mail adaptation: Mail's focused remote-image tests, `cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings`, `cargo test -p calternal-plugin-mail -- --test-threads=4`; prove Notes and Mail use the shared raster transport. 4. If the merge round requests deferred perf, only on the perf VM with the shared release binary: `flock /root/perf.lock bash -c 'uptime; bun bench/browserfix-image-proxy.mjs --runs 20 --json artifacts/browserfix-image-proxy.json'`. Compare docs/perf/baseline.json and add a populated reminder fixture for successful notification lookup. This job made no perf claim. 5. Release, staging/o2 and Mac interop checks remain the orchestrator's merge-round work. Review evidence All 12 #759 screenshots use macOS emulation at 390/820/1440 px in light and dark. Notes/Mail icon cap alignment was inspected using enlarged captured pixels. Visual quality remains for Claude's review. The storage probe adds 24 macOS screenshots of Login, B Mail, B Money and B Appearance. No screenshots or diagnostics were committed. - [mail-clean-dark-1440.png](https://git.kayg.org/attachments/51e3e474-4816-4e30-aa18-27b5407d7788) - [mail-clean-dark-390.png](https://git.kayg.org/attachments/5c29eccd-b4f7-4d07-a33a-3ed0617654ae) - [mail-clean-dark-820.png](https://git.kayg.org/attachments/8a727030-a6b7-47df-819f-1bcc78912bd9) - [mail-clean-light-1440.png](https://git.kayg.org/attachments/0602b896-3355-41e6-96bb-e6235b424994) - [mail-clean-light-390.png](https://git.kayg.org/attachments/80d42776-f351-4af1-aa1f-a009f5085a6f) - [mail-clean-light-820.png](https://git.kayg.org/attachments/877f44ea-57db-477c-a882-b55d6e6133b8) - [note-dark-1440.png](https://git.kayg.org/attachments/ba3a5c97-d524-4632-8cfc-efa7cd41ddd7) - [note-dark-390.png](https://git.kayg.org/attachments/e9e66497-4490-4e96-a98b-453831dc5e23) - [note-dark-820.png](https://git.kayg.org/attachments/4138c09b-a88d-4c29-bbe9-f4f2ba59e9a6) - [note-light-1440.png](https://git.kayg.org/attachments/245ec918-162c-44c3-967d-4d272a776a03) - [note-light-390.png](https://git.kayg.org/attachments/6b6a83f1-2529-411e-a697-d055dc2b68d3) - [note-light-820.png](https://git.kayg.org/attachments/0cd45239-c8bf-4fec-bfae-187f5683710f) - [appearance-b-dark-1440.png](https://git.kayg.org/attachments/647afdf2-8110-4b0b-9343-5c53ee7f973e) - [appearance-b-dark-390.png](https://git.kayg.org/attachments/1842220c-b865-4d73-890e-7b7090d12ee5) - [appearance-b-dark-820.png](https://git.kayg.org/attachments/92a95680-6b9c-43cd-adb7-5a69cdd3f0ac) - [appearance-b-light-1440.png](https://git.kayg.org/attachments/d1fc6f14-fbfd-4bdf-911e-78699f07ce9d) - [appearance-b-light-390.png](https://git.kayg.org/attachments/7d53f634-b761-4cb6-9b57-28e4c23f4950) - [appearance-b-light-820.png](https://git.kayg.org/attachments/93748d50-73d1-4117-89ba-9bc9402e8197) - [login-dark-1440.png](https://git.kayg.org/attachments/2b2149ac-34ab-400d-bef6-a9cdccf7032b) - [login-dark-390.png](https://git.kayg.org/attachments/ab47c7d0-8edc-42bc-993e-555f42ed9370) - [login-dark-820.png](https://git.kayg.org/attachments/3d5df070-4ec2-44e3-97ad-f923709f79e8) - [login-light-1440.png](https://git.kayg.org/attachments/924bd486-92bf-4713-aefd-4ba7c0115d06) - [login-light-390.png](https://git.kayg.org/attachments/6bcc0cc7-d1ac-407e-8f32-6bc72f2f021a) - [login-light-820.png](https://git.kayg.org/attachments/c9851d82-84ca-45b6-9958-eacb9a55645d) - [mail-b-dark-1440.png](https://git.kayg.org/attachments/4f890685-6528-48d5-90ad-3b1e59ef315b) - [mail-b-dark-390.png](https://git.kayg.org/attachments/87170ea1-07d2-4242-a4d7-002589070cc4) - [mail-b-dark-820.png](https://git.kayg.org/attachments/badf2224-e332-4523-a4cd-1c3ff5a5da8e) - [mail-b-light-1440.png](https://git.kayg.org/attachments/6444664f-fa75-430e-b90c-a4a8f2f36c9d) - [mail-b-light-390.png](https://git.kayg.org/attachments/e3eb9274-524d-4d14-9f0d-08f651b2aee1) - [mail-b-light-820.png](https://git.kayg.org/attachments/009b1ee5-10e6-46c4-a046-1eeab3d6272d) - [money-b-dark-1440.png](https://git.kayg.org/attachments/acbb4b87-022e-4fce-878d-74a867a0979a) - [money-b-dark-390.png](https://git.kayg.org/attachments/c3d0419d-58f3-4e5a-ae5c-058740dda83c) - [money-b-dark-820.png](https://git.kayg.org/attachments/e5c5f9f3-526a-4080-8adf-176a1f482a04) - [money-b-light-1440.png](https://git.kayg.org/attachments/8e4c7155-82cb-42c9-bbe4-eec1827e708a) - [money-b-light-390.png](https://git.kayg.org/attachments/6c5264c9-ffc3-4f70-a1a4-82d5747d023e) - [money-b-light-820.png](https://git.kayg.org/attachments/5c16f8e3-1691-41b3-bbac-fe9b4ae2f04b) Doc comments for every authored resume change were re-read. Temporary diagnostic edits were removed. Cleanup completed against only the preset job target: ``` Removed 18391 files, 10.2GiB total Deleted apps/web/build and apps/web/.svelte-kit/output. ``` Working tree has only the two pre-existing untracked viewSnapshots files. Required job gates and both focused production probes pass. READY FOR MERGE: yes (the combined merge round must still validate Mail integration and its full suites).
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#759
No description provided.