Files
LumaWeb/docs/decisions/0005-browser-matrix.md
T
wt c0ce1bd026
Foundation checks / foundation (push) Failing after 2m49s
status
2026-10-15 18:43:29 +07:00

222 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR 0005: Web browser qualification and reference performance passport
- Status: proposed; awaits independent security review of S00-006.
- Task / requirements: S00-006 / NFR-003, NFR-005; specification §27.
- Date: 2026-10-15.
- Decision owner: auto-orchestrator-710c6ba5d8ea (orchestrator).
- Reviewer: assigned security reviewer, not yet recorded.
## Context and decision
Input: accepted ADR 0001, exact requirements, architecture and contract registry,
S00-002 executor/review evidence. Output: one qualification passport with concrete
hardware, browser policy, measurable budgets and permission/fallback procedures.
This decision adds no shared interface, application implementation or dependency.
Budgets are proposed engineering acceptance targets, not observed performance or
a universal SLA. Approval selects reference devices; actual product qualification
requires later browser runs on these devices and the production app.
Select six targets below. The machine-readable table and budget values are in
`experiments/0005-browser-matrix/matrix.json`; this ADR defines their meaning.
| ID | Reference hardware | OS family | Browser / syntax lower bound | CSS viewport / DPR | Modes |
| --- | --- | --- | --- | --- | --- |
| win-chrome | ThinkPad T14 Gen 2 Intel, i5-1135G7, 16 GiB | Windows 11 24H2 x64 | Chrome stable / 111 | 1366×768 / 1 | tab, installed |
| win-edge | Same ThinkPad | Same Windows | Edge stable / 111 | 1366×768 / 1 | tab, installed |
| linux-firefox | Same ThinkPad | Ubuntu 24.04 LTS x64, GNOME Wayland | Firefox stable / 114 | 1366×768 / 1 | tab |
| mac-safari | MacBook Air M1 2020, 8 GiB, 7-core GPU | macOS 15 | Safari stable / 18 | 1280×800 / 2 | tab, installed web app |
| android-chrome | Pixel 6a 128 GB, Tensor, 6 GiB | Android 15 | Chrome stable / 111 | 393×851 / 2.75 | tab, installed |
| ios-safari | iPhone 13 128 GB, A15, 4 GiB | iOS 18 | Safari stable / 18 | 390×844 / 3 | tab, Home Screen |
All measurements use 60 Hz. Desktop SSD, mobile internal storage; leave ≥20%
free. Mobile portrait, standard display scaling/font size, browser UI expanded
at navigation; record actual inner viewport after browser chrome. Desktop set
window inner viewport explicitly. Also test mobile landscape, 320 CSS px width,
wide 1440 CSS px, keyboard/IME, dark mode, 200% text/zoom and reduced motion as
functional variants; they are not substitutes for physical performance devices.
Support policy: qualify the vendor-supported stable build available for each OS
at release cutoff, then freeze its full build in the run record. Floors above
come from ADR 0001, with Safari 18 selected for the named OS families; they do
not authorize using obsolete vulnerable builds online or promise support for all
versions since the floor. If an OS/browser is no longer vendor supported at the
cutoff, revise this passport through review before qualification. Beta/dev,
embedded webviews, other iOS browsers, private profiles and extensions are not
qualified targets. Private/blocked storage must still fail honestly.
Exact patch versions are intentionally resolved at qualification, rather than
inventing future release pins now. Every run MUST supply all `freeze_fields` in
the JSON: full OS/browser build, acquisition source and installer hash (or Apple
OS build), app/asset/fixture hashes, profile reset recipe, tools, time, actual
viewport/DPR/refresh rate and network settings. Archive synthetic fixtures and
build assets with the record. A record saying only “latest” or “stable” is
invalid. Retest the frozen builds for comparison; qualify a new stable version
with a new record after security updates. No automatic acceptance carries over.
Physical devices may be rented/lab-owned; no claim of their availability here.
Emulation/Playwright WebKit alone cannot qualify Safari or mobile hardware.
## PWA and capability policy
Tab mode is primary. Installation is optional progressive enhancement, not a
native Desktop/Android client. No automatic install prompt is assumed. Firefox
uses tab mode in this passport. iOS ordinary Safari tab has no promised Web Push;
Home Screen mode with granted permission, valid subscription and configured
provider is a separate required push scenario. No installation mode promises
continuous sockets/media after hiding, locking, closing or terminating browser.
Resume requires reconciliation; password re-entry/unlock follows its accepted
policy. No offline send/delivery guarantee is introduced.
The JSON `fallbacks` table is normative for all six targets. Probe capabilities
and actual initialization outcomes, not user agent names. Missing required
crypto/secure transport blocks the protected operation. Missing optional media,
location, push, install or biometric capability disables only the dependent
feature and gives a reason. Synthetic fixtures never replace production crypto,
IndexedDB or transports. Storage failure and eviction follow accepted ADR 0004;
this cache budget does not revise persistence schemas or unlock policy.
## Budgets and measurement protocol
| Metric | Desktop | Mobile | Gate |
| --- | --- | --- | --- |
| Cold startup p95 | 2500 ms | 4000 ms | navigation start → usable local shell |
| Warm startup p95 | 1200 ms | 2000 ms | same end point, cached assets/history |
| Action → painted result p95 | 100 ms | 150 ms | open chat, select, type, scroll |
| rAF gap p95 / maximum | 20 / 100 ms | 25 / 150 ms | foreground scripted scroll, 60 Hz |
| Peak app process RAM | 350 MiB | 250 MiB | 10-minute non-call session |
| Retained RAM growth | 30 MiB | 25 MiB | final settled minute minus first |
| Disposable cache ceiling | 128 MiB | 64 MiB | all accounts combined per origin |
Protocol for a later production qualification (not executed in this task):
1. Build production assets with accepted ADR 0001 pins and lockfile. Serve via
valid HTTPS with ordinary certificate validation; no dev server timings. Use
no extensions, other tabs or competing foreground apps; power connected,
power saver off, thermal nominal, ≥50% battery. Record OS/browser builds and
tool versions before runs. Disable diagnostics during timing except required
trace collection. Keep measurements with tracing separately labeled.
2. Deterministic synthetic dataset: one active account, 100 conversations × 100
messages, text `message-00000` through `message-09999`, each padded with ASCII
`x` to 512 characters; 50 visible messages in selected chat. No real JIDs,
credentials or conversations. Seed through future test fixture adapters, not
production authentication bypass. Hash the exact serialized fixture and seed
script; check local row count before each run. Cache workload: 1 MiB synthetic
byte blobs until the ceiling plus two blobs. No audio/video running in the RAM
workload; call/media owners must later define separate active-call budgets.
3. Controlled network: 10 Mbit/s download, 2 Mbit/s upload, 80 ms round-trip,
zero intentional loss, no CPU throttle. Use a recorded external shaper for
physical devices; record tool version/config and measured throughput/RTT to
fixture host. WSS/MAM/server latency is measured separately and not part of
usable-local-shell time. “Ready” means conversation list and 50 local messages
painted, composer focusable and input accepted. Login/unlock/password human
time is excluded and reported separately. Never mark connected/crypto ready
merely to satisfy this local timing gate.
4. Thirty cold runs: retain seeded IndexedDB, clear HTTP/disposable asset cache
and unregister prior service worker, terminate browser, reopen at target URL.
“Cold” here means application asset cold, not OS disk cold. Thirty warm runs:
first prime assets/worker and fixture, close tab and reopen with same profile.
Installed/Home Screen mode repeats both sequences and reports separately.
Record navigation start and app-ready mark using Performance API; confirm
visible ready UI in trace/screenshot. Report raw runs, failures and p95 via
sorted nearest-rank ceil(0.95*n). Do not discard slow runs or mix modes.
5. For each mode collect 100 actions each (open chat, select message, type one
character, scroll one viewport), paced 500 ms apart; 60 seconds continuous
deterministic scroll. Measure input timestamp → frame containing changed UI
with browser performance trace. A double-rAF marker is only a proxy and must
be labeled. Collect rAF timestamps in foreground; compute consecutive gaps,
p95 and maximum. Record hidden/throttled runs separately, not as foreground
passes. Any action failure fails function regardless of percentile.
6. RAM: record baseline blank profile browser process tree, then sample app
renderer/WebContent plus dedicated workers every second during 10 minutes of
repeated chat switching/scrolling. Chromium task manager/process trace,
Firefox about:processes plus OS RSS, Safari WebContent/worker OS RSS (macOS
Activity Monitor; iOS Instruments on physical device). Use isolated browser
profile so shared workers are attributable. Report sum RSS and baseline
separately, not JS heap as total RAM. Peak must fit ceiling; last-minute
median minus first-minute median must fit growth budget, with no forced GC.
If attribution/tool access is unavailable, RAM gate is unverified, never 0.
7. Cache: sum owned disposable asset/media payload bytes with adapter inventory;
compare browser origin storage estimate as advisory, not exact cache size.
Limit is min(reference ceiling, 10% reported quota) when estimate available;
if unavailable, use reference ceiling and stop on actual quota error. Before
admitting another blob evict disposable LRU entries or reject the write;
never exceed budget deliberately. History, crypto/trust and pending unsynced
work are excluded from disposable cache and must not be evicted by this gate.
Confirm counts after reopen; inject QuotaExceededError and test actual device
low-disk failure separately. No checkpoint advance on save failure. Browser
may evict the entire origin: verify explicit recovery state after user clear.
Pass each target/mode independently; a fast desktop cannot offset mobile failure.
These numbers are initial product targets chosen for responsiveness and bounded
resources, not a benchmark derived from the small S00-002 counter. Later measured
infeasibility requires reviewed budget/device change, not silently relaxing gates.
## Contextual permissions and negative scenarios
For each target AND mode, run the following against a trusted HTTPS test origin
with synthetic identities. Fresh profile has no prompts at startup. Trigger only
via explicit relevant action: Start call/record (mic/camera), Send location
(geolocation), Enable notifications (push), Enable unlock (only S00-007 policy).
Storage persistence request must explain context rather than masquerade as login.
| State | Reproduction | Required observation |
| --- | --- | --- |
| prompt / granted | Reset site and OS permissions; invoke each action and grant | Only requested capability activates; no unrelated permission prompt |
| denied | Deny browser prompt, then deny at OS level separately | Reason/actionable settings path; text still usable; no retry prompt loop |
| hardware missing | Disconnect USB camera/mic on desktop; use OS-disabled device on mobile | Capture fails honestly; camera-only failure may offer working audio |
| revoked during session | Start capture then revoke site/OS permission; unplug device; separately revoke location/notification permission | End/reconcile capture, stop watches; clear usable/push state on observed failure or resume; no stale success |
| unsupported | Disable API via test-only fixture boundary or use naturally absent mode | Each JSON fallback observed, protected operations remain blocked |
| ignored prompt / busy | Leave prompt unanswered then cancel action; separately hold camera in another app | Pending is cancellable; late grant is stopped/ignored; busy error not labeled denial |
| lifecycle / loss | Hide, lock, sleep, offline, close, reopen; remove subscription and clear origin | No continuous session promise; resync/unlock and explain lost local state |
A Permissions API query may be unsupported for a descriptor: catch failure and
rely on real API result and track/error events; recheck on focus/resume. Revocation
may arrive through track ended or subsequent failed operation, not a uniform
permission event. Include denied→settings grant→explicit retry and account switch
while prompt pending. Do not log coordinates, device labels, endpoints or secrets.
Screenshots contain synthetic UI only. Browser/OS policy injection is labeled as
fixture evidence; it does not prove physical device revoke or hardware absence.
## Candidates, sources and constraints
Primary sources inspected 2026-10-15; these describe mechanisms, not results on
our selected devices:
- [Vite build compatibility](https://vite.dev/guide/build.html): syntax floors and
lack of automatic API polyfills. The matrix does not change ADR 0001 targets.
- [Chrome release channels](https://developer.chrome.com/docs/web-platform/chrome-release-channels):
stable qualification is chosen over beta/dev; exact run builds still required.
- [WebKit iOS Web Push](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/):
Home Screen eligibility and direct user interaction for permission requests.
- [Media Capture and Streams](https://www.w3.org/TR/mediacapture-streams/):
permission/device errors and track lifecycle underpin media negative scenarios.
- [Permissions](https://www.w3.org/TR/permissions/): permission state and change
handling; implementation support for individual descriptors remains conditional.
- [Storage Standard](https://storage.spec.whatwg.org/): persistence/estimates are
browser controlled; ceilings are application policy, not guaranteed disk quota.
- [Web Authentication Level 3](https://www.w3.org/TR/webauthn-3/): authenticator
operations do not establish a universal browser hardware-backed storage vault.
Rejected: Chromium-only coverage (misses independent engines/iOS behavior),
“all modern browsers” without reference hardware, emulator-only performance,
unconditional PWA push/background claims and frozen obsolete online binaries.
A broad rolling channel without recorded full builds is also insufficient.
Selected: bounded three-engine/six-target qualification with immutable run records.
No added libraries: dependency license review is not needed for this document and
stdlib checker. Existing stack/library notices remain with their respective ADRs.
No secrets, crypto implementation, network proxy, CORS/TLS exception or application
module is introduced. S01 contracts and downstream performance/browser/provider
implementation tasks retain ownership of exact adapter and telemetry interfaces.
## Focused experiment and actual result
`python3 experiments/0005-browser-matrix/check.py` validates the passport's target
coverage, reproduction fields, budget consistency and fallback coverage; negative
mutations prove incomplete targets, missing fallback and relaxed guards fail.
This is document/fixture validation only. No physical reference device, browser
permission behavior, production startup/RAM/render/cache measurement or provider
integration was executed. Their procedures above are external qualification
checks, not passed results. S00-006 selects a passport; it does not certify release.
Acceptance/qualification failure, OS vendor support ending, new engine/API need,
or changed resource workload requires a separately reviewed passport revision.