222 lines
15 KiB
Markdown
222 lines
15 KiB
Markdown
# 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.
|