diff --git a/docs/release-control/v6/internal/records/resource-payload-static-metadata-2026-08-24.md b/docs/release-control/v6/internal/records/resource-payload-static-metadata-2026-08-24.md new file mode 100644 index 000000000..4c93fac64 --- /dev/null +++ b/docs/release-control/v6/internal/records/resource-payload-static-metadata-2026-08-24.md @@ -0,0 +1,52 @@ +# Resource payload static-metadata bloat (browser performance audit, 2026-08-24) + +## Context + +A read-only browser performance audit (2026-08-23/24, production build served by +the dev backend against the 50-node / 929-guest / 1,508-resource mock estate) +measured the client-facing resource stream as a primary scalability cost: + +- The full state snapshot is **4.73 MB** of JSON in a single websocket frame + (`/api/state` REST recovery returns the same payload). `resources` accounts + for 4.63 MB — ~3 KB average per resource. +- Idle on the Proxmox overview, the browser main thread was blocked for + **12.0 s out of every 45 s** (individual long tasks of 0.8–1.4 s roughly + every 3–4 s, aligned with resource delta frames of up to ~500 KB) on an + unthrottled M-series desktop. Under a 4x CPU throttle (mid-range phone + class), tab taps landing inside those windows blocked for multiple seconds. + +## Measured payload composition (per-field bytes across 1,508 resources) + +| Field | Bytes | Notes | +| --- | --- | --- | +| `canonicalIdentity` | 0.82 MB | 12 aliases incl. 7 superseded hash ids on a typical VM; identity history ships on every snapshot | +| `proxmox` + `platformData` | 1.36 MB | platform payloads | +| `capabilities` | 0.34 MB | only **10 distinct blobs** across all 1,508 resources — duplicated per resource, including human-readable `description` strings | +| `aiSafeSummary` | 0.14 MB | AI-oriented prose on every resource, shipped to every browser client | +| `policy` | 0.12 MB | near-constant routing metadata per resource | + +The websocket delta path is already field-level (JSON merge patches in +`internal/websocket/state_delta.go`), so delta size is driven by estate scale, +not by a diffing defect. The static-metadata weight is paid on every full +snapshot, REST recovery, reconnect, and mobile tab resume. + +## Why this is a governed gap rather than a quick fix + +Slimming the stream means changing the client-facing resource shape (for +example: a capabilities catalog referenced by id instead of inlined blobs, +identity history behind a detail endpoint instead of inline aliases, and +audience-scoped fields so `aiSafeSummary`/`policy` do not ship to browser +sessions that never read them). That is a wire-format change with consumers +beyond frontend-modern — pulse-mobile (OTA-before-server-release constraint), +Pro/enterprise surfaces, and the AI runtime — and payload fields are not +contract-neutral under the governance rules. It needs an owned slice with +contract updates and cross-client verification, not an opportunistic patch. + +## Companion fixes already landed separately + +- Nav tab identity stabilization (`frontend-modern/src/components/shared/stableNavTabs.ts`) + so websocket ticks no longer recreate every nav button. +- `preloadDynamicChunks: false` in `frontend-modern/vite.config.ts` so cold + start no longer fetches and compiles all ~3.1 MB of lazy chunks up front. +- Per-tab scoped hydration and realtime gating on the Proxmox surface + (`17bb2b3b7`, Performance lane). diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index ab41309d4..eb99948ec 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -9690,7 +9690,44 @@ "subsystem_ids": [] } ], - "coverage_gaps": [], + "coverage_gaps": [ + { + "id": "resource-payload-static-metadata", + "summary": "The client-facing resource stream ships ~3KB of mostly-static metadata per resource (duplicated capabilities blobs with only 10 distinct values across 1,508 resources, canonicalIdentity alias/superseded-id history, aiSafeSummary prose, and policy routing) on every snapshot, REST recovery, and reconnect. At 50-node scale the snapshot is a single 4.73MB websocket frame and browser main-thread cost per delta reaches ~1s even on desktop hardware. Slimming the stream is a wire-format change with consumers beyond frontend-modern (pulse-mobile OTA compatibility, Pro surfaces, AI runtime), so it needs an owned slice with contract updates and cross-client verification.", + "owner": "project-owner", + "status": "triaged", + "recorded_at": "2026-08-24", + "lane_ids": [ + "L6", + "L10", + "L13" + ], + "subsystem_ids": [ + "api-contracts", + "performance-and-scalability", + "unified-resources" + ], + "proposed_resolution": "lane-expansion", + "coverage_impact": 6, + "evidence": [ + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/resource-payload-static-metadata-2026-08-24.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/stores/websocket.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/websocket/state_delta.go", + "kind": "file" + } + ] + } + ], "candidate_lanes": [], "work_claims": [], "open_decisions": [],