koala73 / koala73/worldmonitor

perf(bootstrap): eliminate redundant transfer and enforce startup payload budgets

Open
#7,045 3 comments 0 reactions 0 assignees View on GitHub
agent-readiness area:frontend epic P1 performance
Dominant language
TypeScript
Stars
86.6k
Forks
13.1k
Avg merge
8h 4m
Merged PRs (30d)
825

Description

---
title: "Bootstrap Transfer Efficiency - Plan"
type: perf
date: 2026-08-21
deepened: 2026-08-21
artifact_contract: ce-unified-plan/v1
artifact_readiness: implementation-ready
product_contract_source: ce-plan-bootstrap
execution: code
depth: deep
---

# Bootstrap Transfer Efficiency - Plan

## Goal Capsule

Reduce the dashboard's startup transfer without removing data or extending a client deadline. The work must stop post-hydration duplicate requests, keep deferred datasets out of universal tier payloads, make fast-tier size a controlled product constraint, and record enough completion and byte evidence to separate a full response from an aborted partial response.

Authority order:

1. The user-confirmed scope in this plan.
2. The bootstrap and panel lifecycle contracts in `CONCEPTS.md` and the repository instructions.
3. Current production evidence from DebugBear analysis `86158947` and the adjacent scheduled runs.
4. Existing implementation patterns, especially circuit-breaker warming and `ensureHydrated()`.

Stop and surface a blocker if an implementation would:

- increase the web `fast` or `slow` timeout above 1,200 ms or 3,000 ms without a new field-data decision;
- remove a dataset from a variant instead of changing when it loads;
- make a public on-demand CDN shield longer than the key's freshness budget;
- replace the one-shot hydration handoff with an unbounded global client cache;
- treat a partial or aborted DebugBear response as a completed payload-size sample;
- put mutable production performance observations into a pull-request pass/fail check.

Execution profile: three tracked implementation issues and pull requests, followed by one production acceptance window. Land the evidence and guardrails first. Then land hydration reuse and tier rebalancing. The final executor owns CI repair and ready-PR delivery for its issue; merge and auto-merge remain separate decisions.

Tail ownership: the parent issue remains open until post-deploy evidence satisfies the Production Acceptance Gate. A green pull request proves code readiness, not production acceptance.

## Product Contract

### Summary

The dashboard currently pays twice for some bootstrap data. A tier can finish and populate the one-shot hydration cache, then a later loader drains the value and a repeated loader call falls through to the RPC or per-key bootstrap URL. The slow tier also ships large energy registries before their deferred panels or disabled map layers need them. At the same time, the fast tier is close enough to its 1,200 ms deadline that the scheduled mobile profile reaches the cutoff and triggers fallback requests.

The fix keeps one-shot hydration but transfers accepted data into the cache owned by each recurring service. It moves `pipelinesGas`, `pipelinesOil`, and `storageFacilities` to the existing public on-demand path. It also applies a measured, deterministic size contract to the fast and slow tiers and replaces the concluded R2-only RUM fields with general bootstrap completion and byte evidence.

### Problem Frame

DebugBear's 2026-08-21 email reported `Fetch Request Changes +366 KB`, including `+337 KB` for the slow bootstrap, `+32.7 KB` for natural events, `+31.2 KB` for the news digest, and `-29.1 KB` for the weather-alert key. Those request-level deltas do not prove a 366 KB code regression.

The matched scheduled analyses show why:

- Ajax transfer moved from 1,433,062 bytes on analysis `86080141` to 1,533,777 bytes on `86158947`: +100,715 bytes, or +98.4 KiB.
- The slow tier alternates between smaller cutoff-like responses and full responses. It transferred 236,456 bytes in `86080141` and 351,175 bytes in `86158947`, while its decoded body moved from 1,218,683 to 1,937,018 bytes.
- The fast tier transferred 149,122 bytes and decoded to 766,645 bytes in 1,228 ms. The web abort deadline is 1,200 ms.
- After the tier requests, six per-key or RPC fallbacks transferred 118,688 bytes. The slow-tier repeats for natural events, wildfires, and sanctions transferred 83,517 bytes and started after the slow tier had completed.
- The slow bundle contains 67 keys and about 1.94 MB decoded. `pipelinesOil`, `pipelinesGas`, and `storageFacilities` account for about 528 KB decoded and an estimated 94 KiB on the wire.
- The news digest body changes with the feed. It measured 252,044 decoded bytes in the scheduled trace and 181,924 bytes later that morning. This is content variance, not enough evidence for a response-shape change.

The product defect is therefore a missing transfer contract, not one unexplained email number. A completed tier must prevent immediate duplicate reads, universal tiers must contain only universal startup data, and monitoring must compare completed responses with completed responses.

### Actors

- A1. Anonymous and signed-in web visitors on all product variants.
- A2. Desktop visitors using the longer existing desktop deadlines and persistent fallback cache.
- A3. Deferred panels and map layers that can mount or turn on after the initial data fan-out.
- A4. The bootstrap publisher, public Edge endpoint, and CDN cache.
- A5. DebugBear scheduled tests and sampled RUM used for production acceptance.
- A6. An implementation agent executing one tracked issue at a time.

### Requirements

#### Transfer lifecycle

- R1. Reuse accepted hydration. When a recurring service accepts a valid hydrated value, later calls within that service's cache TTL must reuse its owner cache and must not issue an RPC or per-key bootstrap request for the same logical dataset.
- R2. Preserve one-shot ownership. `getHydratedData()` remains a read-once handoff. Reuse belongs in the consumer's circuit breaker or bounded service cache, not in a global permanent hydration map.
- R3. Preserve deadlines. Web tier deadlines remain `fast=1,200 ms` and `slow=3,000 ms`; desktop remains `fast=5,000 ms` and `slow=8,000 ms` unless a later, separately approved field study changes them.

#### Tier composition and compatibility

- R4. Defer the energy registries. `pipelinesGas`, `pipelinesOil`, and `storageFacilities` must leave the slow bundle and load through `?keys=&public=1` when the relevant layer or panel needs them.
- R5. Preserve data and recovery. Full, energy, finance, commodity, desktop, and late-mounted-panel flows must still render the same records. Offline persistent-cache fallback, stale-if-error behavior, retries, and honest failure states must remain intact.
- R6. Bound tier weight. A deterministic report must expose serialized bytes by key and by tier. The checked-in fast reference fixture must be at least 20% smaller than the pre-change reference, and the checked-in slow reference must exclude the three energy registries and be at least 25% smaller than its pre-change reference.

#### Evidence and regression prevention

- R7. Record completion and bytes. The sampled custom telemetry payload must record one selected tier per page, the closed outcome, elapsed time, decoded response bytes, best-effort encoded bytes, and device class. It must contain no stable user or request identifier.
- R8. Compare valid cohorts. Production acceptance must report aborts separately and calculate byte distributions only from responses that completed and parsed. Traffic-weighted results are the release headline; thin variant or region cells are inconclusive, not pass or fail.
- R9. Prevent recurrence. Tests must fail when a recurring hydrated service bypasses its owner cache, when a deferred energy registry re-enters a universal tier, when on-demand cache age exceeds freshness, or when reference tier weight exceeds its budget.

### User and System Flows

#### F1 — Web cold boot with both tiers complete

1. The browser fetches the fast tier and parses it before 1,200 ms.
2. The slow tier starts after the next paint and parses before 3,000 ms.
3. Initial loaders consume their hydration once and warm the relevant service cache.
4. Viewport hydration, layer toggles, or refresh scheduling call the same service again.
5. The service returns its owner-cache value and sends no duplicate network request within TTL.

#### F2 — A tier times out or fails

1. The tier aborts at its existing deadline or fails to parse.
2. Bootstrap reads the bounded persistent tier cache when eligible.
3. A consumer without usable hydration follows its current CDN-shielded or RPC fallback.
4. Telemetry records `abort`, `network-error`, `parse-error`, or `cached-fallback`; it does not record a partial body as a completed byte sample.

#### F3 — A deferred energy surface becomes relevant

1. A pipeline or storage panel approaches the viewport, or a relevant map layer is enabled.
2. Its shared registry store drains any hydration left by an older deployment, then calls `ensureHydrated()` for the on-demand key when needed.
3. The public per-key URL supplies the complete registry behind a freshness-correct CDN policy.
4. The panel or map renders the same records and keeps its existing background refresh behavior.

#### F4 — An energy-focused variant starts with an energy layer enabled

1. The universal slow tier does not contain the energy registry.
2. The enabled layer creates immediate demand, so the existing loader starts the public per-key fetch during that variant's startup.
3. Other variants that do not render the layer transfer zero bytes for that registry.

#### F5 — Desktop or offline startup

1. Desktop uses its existing 5,000/8,000 ms limits.
2. An offline client can still use the persistent tier snapshot.
3. A moved on-demand key can use its existing service or registry fallback and must not turn a recoverable outage into a confident empty state.

#### F6 — Production comparison

1. Capture a pre-change RUM baseline for at least one full daily traffic cycle after the telemetry issue deploys.
2. Deploy the behavior changes.
3. Capture a post-change window with the same sampling and outcome vocabulary.
4. Compare completed byte samples, outcome rates, and scheduled DebugBear requests. Do not combine an aborted body with a complete body.

### Acceptance Examples

- AE1. Given a complete slow tier with natural events and wildfires, when `loadNatural()` runs twice after the slow checkpoint, the first call consumes hydration and the second call causes zero natural-event and wildfire network requests.
- AE2. Given a complete fast tier with earthquakes, when a viewport hydration pass repeats the natural loader, the earthquake circuit breaker returns the accepted hydrated response and no seismology RPC is sent within TTL.
- AE3. Given a fast-tier abort, when weather and insight consumers have no accepted hydration, their existing fallback requests still run and the telemetry outcome is not `complete`.
- AE4. Given the full variant with pipeline and storage layers disabled and their panels below the viewport, initial fast and slow responses contain none of the three energy registries and no per-key energy request starts.
- AE5. Given the energy variant with a pipeline layer enabled, the registry loads from the public per-key URL and the map and panel render the same registry content as before.
- AE6. Given a late-mounted pipeline panel, a response that arrived before insertion is stored and replayed through the shared registry store; the panel does not remain in a loading or empty state.
- AE7. Given an aborted scheduled slow request, its partial transfer is counted in the abort-rate report but excluded from completed-payload median and percentile calculations.
- AE8. Given a news digest size increase with no stable matched-body evidence, this work records it as an observation and does not change the digest contract.

### Success Criteria

- The representative mobile browser test observes no repeat request for any dataset that was accepted from bootstrap and called again within TTL.
- The fast reference fixture is at least 20% smaller and still contains every key proven necessary before first paint.
- The slow reference fixture is at least 25% smaller and does not contain `pipelinesGas`, `pipelinesOil`, or `storageFacilities`.
- In the post-deploy RUM window, at least 99% of traffic-weighted fast samples and 99% of slow samples have the `complete` outcome. Abort, HTTP, network, parse, and cached-fallback outcomes remain separate. Any well-sampled device or variant cohort must not regress by more than 0.5 percentage points from baseline.
- In three consecutive scheduled production runs with both tiers complete, the combined transfer for fast bootstrap, slow bootstrap, and their duplicate follow-up requests is at least 150 KiB lower than the completed pre-change reference, with no additional missing dashboard data.

### Scope Boundaries

In scope:

- bootstrap consumer cache warming and repeat-call tests;
- deterministic per-key and per-tier byte accounting;
- general bootstrap completion and byte RUM using the existing sampled DebugBear queue;
- demoting the three energy registries to on-demand;
- shrinking the fast tier by demoting non-immediate keys or publishing a compact Bootstrap View Key when the full data is needed elsewhere;
- browser and production verification across relevant variants.

Out of scope:

- increasing timeout constants;
- reopening the R2-origin cutover in #5338;
- changing Redis, R2, or Workers KV authority;
- removing panels, layers, sources, records, or desktop support;
- changing the news digest response based on this single variable-content alert;
- putting live DebugBear or RUM thresholds in pull-request CI;
- redesigning all bootstrap tiers by variant. A future issue can add variant-scoped tier objects if the work here cannot give the fast tier enough headroom.

### Dependencies

- `ensureHydrated()` and public single-key bootstrap serving remain available.
- DebugBear RUM remains enabled at the existing 10% sample rate and its three metric and three tag slots can be repurposed from the concluded R2 experiment.
- The production acceptance window begins only after the telemetry vocabulary is deployed and validated.
- The on-demand move must pass `tests/bootstrap-on-demand-cache-budget.test.mts` against health freshness budgets.

## Planning Contract

### Key Technical Decisions

- **KTD1 — Fix consumers, not the one-shot cache.** Keep `getHydratedData()` consume-once. Put the hydration read inside the service's circuit-breaker execution or call `recordSuccess()` after validation. Services with an established bounded local cache use that cache. This follows the existing weather, prediction, risk-score, theater-posture, commodity, and sector patterns.
- **KTD2 — Audit every recurring direct-return consumer.** The mandatory observed set is `naturalEvents`, `wildfires`, `earthquakes`, and `sanctionsPressure`. The implementation classifies each other direct-return consumer as recurring, one-shot, or already cached. Only recurring consumers change. The pull request records this bounded classification so the audit cannot silently expand into a service rewrite.
- **KTD3 — Reuse the on-demand route.** Move the energy keys by changing the canonical tier registry and consumer stores. Do not add another endpoint or send their misses to an unshielded RPC. Add explicit CDN profiles only where the default two-hour shield would exceed freshness.
- **KTD4 — Keep immediate behavior, not universal freight.** If a variant needs an energy registry at startup, its enabled layer or immediate panel creates an on-demand request. That is acceptable because only the variant that renders the data pays for it.
- **KTD5 — Size before changing fast membership.** Generate a deterministic byte ledger and classify fast keys by first-paint need. Demote a key when all consumers are deferred or disabled at startup. If an immediate consumer needs only a subset, publish a Bootstrap View Key and keep the canonical payload unchanged for RPC, MCP, and later detail reads.
- **KTD6 — Replace obsolete experiment telemetry.** Retire the R2-only client classifier and reuse its DebugBear queue slots for general bootstrap evidence: `metric1=duration_ms`, `metric2=decoded_bytes`, `metric3=encoded_bytes_or_minus_one`; `tag1=tier`, `tag2=outcome`, `tag3=device_class`. Select one tier per page as today, keep the 10% sample, and send no identifier. Public bootstrap responses may expose resource timing through `Timing-Allow-Origin`; telemetry still records `-1` instead of guessing when encoded bytes are unavailable.
- **KTD7 — Separate code gates from production gates.** Deterministic payload and request-count tests gate pull requests. DebugBear and RUM decide production acceptance after deploy. A request is a completed byte sample only after its JSON body parses and passes the payload-shape check.
- **KTD8 — Use traffic-weighted acceptance.** Global traffic-weighted completion is the release headline. Device, variant, and region views diagnose regressions only when both baseline and candidate cells have enough samples; otherwise report `inconclusive`.

### High-Level Technical Design

```mermaid
flowchart LR
B["Public bootstrap tier"] --> H["One-shot hydration map"]
H --> C["Consumer validates value"]
C --> O["Consumer-owned breaker or bounded cache"]
O --> R["Repeated loader call returns cached data"]
R -. "no request within TTL" .-> O

D["Deferred panel or disabled layer"] --> E["ensureHydrated key public=1"]
E --> P["Per-key CDN object"]
P --> S["Shared registry store"]
S --> V["Panel or map render"]

T["Tier fetch"] --> M{"Body parsed?"}
M -->|yes| X["complete plus byte sample"]
M -->|no| Y["abort or error only"]
```

### System-Wide Impact

- **Data lifecycle:** hydration stays a one-time ownership transfer. Recurring services become responsible for retaining accepted data for their documented TTL.
- **Network:** the full dashboard avoids known follow-up requests and stops paying for deferred energy registries. Energy-focused variants may start a scoped per-key request earlier than other variants.
- **Caching:** public per-key CDN caching becomes load-bearing for the moved registries. Freshness profiles must remain no longer than seed-health budgets.
- **Observability:** the client telemetry changes from an abandoned R2 comparison to general tier completion and size. Existing DebugBear quota and privacy limits remain unchanged.
- **Desktop:** tier membership changes affect desktop payload composition, but desktop deadlines and offline tier cache remain unchanged.
- **Agent parity:** no user action or tool surface changes. The issue bodies and U-IDs are the execution interface for later agents.

### Sequencing and Issue Split

1. **Evidence issue:** U1. Deploy it first and collect the pre-change daily baseline.
2. **Hydration reuse issue:** U2. It can be implemented independently, but its production comparison uses the U1 vocabulary.
3. **Tier rebalancing issue:** U3 and U4. Start after U1. Keep it separate from U2 because it changes canonical tier ownership and variant behavior.
4. **Parent acceptance:** U5 closes only after both behavior issues deploy and the post-change window is complete.

Do not implement U2 and U3/U4 in the same pull request. Their local files mostly differ, but separate changes make request removal and tier-size effects attributable.

### Risks and Mitigations

- **Risk: stale on-demand energy data.** The default on-demand CDN profile is two hours. Compare each moved key with `api/health.js` and add an explicit profile when required.
- **Risk: hydration caches an empty or degraded response.** Reuse the service's existing `shouldCache` predicate or validate non-empty, data-available content before `recordSuccess()`.
- **Risk: late-mounted panels lose the result.** Exercise construct/detached, response-arrives, mount, and render order through the shared registry store and existing late-mount test pattern.
- **Risk: a fast-tier size win creates a new request at startup.** Count the complete startup request set in browser tests. A demotion is valid only if its consumer is not immediate, or the compact view prevents the new request.
- **Risk: deterministic fixtures drift from production.** Use fixtures as regression gates and publisher/RUM byte ledgers as operational truth. Do not copy mutable production values into CI as exact expected bodies.
- **Risk: custom RUM slots are still used operationally for R2.** Confirm the R2 experiment is concluded and update tests, comments, and dashboard mappings together before repurposing them.
- **Risk: the RUM project attaches session identity outside these custom fields.** Verify the project's session-tracking setting before admitting samples to the acceptance record. If the setting does not meet the approved privacy posture, keep the code disabled and use scheduled lab evidence until a privacy-safe collector is approved.
- **Risk: a scheduled lab run compares different content.** Compare bootstrap endpoints and their follow-up requests, require complete tier bodies, and keep the volatile news digest outside the acceptance calculation.

### Sources and Research

- `src/services/bootstrap.ts` — one-shot cache, tier deadlines, deferred slow fetch, persistent fallback, and current R2-only RUM hook.
- `shared/bootstrap-tier-keys.js` — canonical fast, slow, and on-demand ownership.
- `src/utils/circuit-breaker.ts` — `recordSuccess()` and bounded in-memory/persistent cache behavior.
- `src/services/eonet.ts`, `src/services/wildfires/index.ts`, `src/services/earthquakes.ts`, `src/services/sanctions-pressure.ts` — observed direct hydration paths that can fall through on repeat calls.
- `src/services/weather.ts`, `src/services/prediction/index.ts`, `src/services/cached-risk-scores.ts`, `src/services/cached-theater-posture.ts`, `src/services/market/index.ts` — accepted cache-warming patterns.
- `shared/pipeline-registry-store.ts`, `shared/storage-facility-registry-store.ts`, `src/components/PipelineStatusPanel.ts`, `src/components/StorageFacilityMapPanel.ts` — deferred energy ownership and late refresh behavior.
- `src/app/panel-layout.ts` and `docs/solutions/ui-bugs/deferred-panel-mounts-after-the-boot-data-pass-and-keeps-its-constructor-empty-state.md` — deferred mount lifecycle.
- `scripts/publish-bootstrap-tiers.mjs` and `tests/publish-bootstrap-tiers.test.mjs` — deterministic payload assembly and current aggregate write-byte log.
- `tests/bootstrap.test.mjs`, `tests/bootstrap-runtime.test.mts`, `tests/bootstrap-on-demand-cache-budget.test.mts`, and `tests/hydration-lock-keys.test.mts` — current bootstrap contracts.
- `docs/solutions/best-practices/egress-cost-tracks-origin-miss-rate-not-client-count.md` — lever test and one-shot hydration fallthrough precedent.
- `docs/solutions/best-practices/gate-rollouts-on-traffic-weighted-data-not-a-hand-picked-cohort.md` — production gate denominator and thin-cell rule.
- `docs/solutions/2026-07-14-bootstrap-r2-timeout-measurement.md` — concluded no-go and the rule against changing timeout constants without adequate evidence.
- Closed issue #5300 — prior demand-driven bootstrap contract and the original energy-registry on-demand intent.
- Open issue #5165 — related post-hydration main-thread work, but not a duplicate of this transfer issue.
- Open issue #5338 — concluded R2-origin follow-up; explicitly outside this plan.
- DebugBear scheduled analyses `86080141` and `86158947`, page `693057`, UK mobile profile, 2026-08-20 and 2026-08-21.

## Implementation Units

### U1. Make tier weight and completion measurable

**Goal:** create deterministic pull-request guardrails and production evidence before changing transfer behavior.

**Requirements:** R6, R7, R8, R9.

**Files:**

- `scripts/publish-bootstrap-tiers.mjs`
- `tests/publish-bootstrap-tiers.test.mjs`
- `api/bootstrap.js`
- `src/services/bootstrap.ts`
- `src/bootstrap/bootstrap-transfer-rum.ts`, replacing `src/bootstrap/bootstrap-r2-rum.ts`
- `src/bootstrap/debugbear-rum.ts`
- `tests/bootstrap-transfer-rum.test.mts`, replacing `tests/bootstrap-r2-rum.test.mts`
- `tests/bootstrap-runtime.test.mts`
- `tests/fixtures/bootstrap-payload-budget.mjs`
- `tests/bootstrap-payload-budget.test.mjs`

**Approach:**

1. Add a pure byte-ledger function that serializes the exact public payload shape and returns aggregate bytes plus ordered per-key bytes. Use UTF-8 byte length, not JavaScript character count.
2. Make the publisher log the aggregate and the largest keys as structured, bounded fields. Do not log data values.
3. Add checked-in representative fast and slow fixtures and a budget manifest containing pre-change byte ceilings, final target totals, and any approved per-key exception with rationale. U1 enforces the pre-change ceilings so it can land before behavior changes. U3 and U4 ratchet the enforced ceilings to the final targets in the same changes that remove the bytes. Pin minimum record counts and required fields so shrinking the fixture cannot manufacture a passing result.
4. Replace the R2-only sample builder with a general tier outcome builder. Record `complete`, `abort`, `http-error`, `network-error`, `parse-error`, and `cached-fallback` with one selected tier per page.
5. Measure decoded bytes from the response text before JSON parsing. Add `Timing-Allow-Origin` only to the public bootstrap response shape, then record best-effort encoded bytes from its `PerformanceResourceTiming` entry. Use `-1` when the browser cannot expose it. Never substitute decoded bytes into the encoded field.
6. Keep the existing 10% presampling and closed device class. Update the DebugBear custom-field mapping documentation and tests in the same change.
7. Verify the DebugBear project's session-tracking and privacy setting before enabling the custom fields as acceptance evidence.
8. Capture and retain one full daily pre-change baseline before the behavior issues deploy.

**Test Scenarios:**

- A multibyte fixture reports UTF-8 bytes correctly.
- Fixture self-checks fail if representative record counts or required fields are reduced.
- Missing and malformed Redis values appear in `missing` and not in per-key data bytes.
- A complete, parsed response emits byte metrics once for the selected tier.
- Abort before headers, abort during body read, non-2xx, parse failure, and persistent-cache fallback emit distinct outcomes and do not claim completed bytes.
- The other tier on the same page cannot overwrite the selected tier's custom slots.
- The deterministic budget fails on aggregate overflow and on an unexplained material per-key growth.

**Verification:** run the focused publisher, bootstrap runtime, and RUM tests in the Verification Contract. Inspect a local payload report and confirm that it contains key names and sizes only.

### U2. Retain accepted hydration in recurring service caches

**Goal:** eliminate the known post-tier duplicate requests without changing one-shot hydration or fallback behavior.

**Requirements:** R1, R2, R5, R9.

**Files:**

- `src/services/eonet.ts`
- `src/services/wildfires/index.ts`
- `src/services/earthquakes.ts`
- `src/services/sanctions-pressure.ts`
- recurring direct-return consumers found by the bounded audit, including `src/services/aviation/index.ts`, `src/services/supply-chain/index.ts`, `src/services/social-velocity.ts`, `src/app/data-loader.ts`, and `src/services/market/index.ts` when applicable
- focused service tests under `tests/`
- `tests/hydration-lock-keys.test.mts`
- `tests/bootstrap.test.mjs`

**Approach:**

1. Inventory every `getHydratedData()` call whose enclosing loader can run again within the cache TTL. Record a bounded classification table in the pull-request description: recurring and uncached, recurring and cached, or one-shot.
2. For breaker-backed services, perform hydration validation inside `breaker.execute()` or call `breaker.recordSuccess()` with the exact cache key used by later calls.
3. For services with an existing local cache, store the accepted hydrated value there. Do not add a second cache if the service already owns one.
4. Preserve non-empty and freshness validation. Do not cache an empty fallback, a degraded response, or a value that the current path rejects.
5. Add an injection seam only where needed to prove network call counts. Avoid a new shared abstraction unless at least three services can use the same typed contract without adapters.
6. Keep `runGuarded()` as an in-flight lock. Do not treat it as a completed-result cache.

**Test Scenarios:**

- Each mandatory observed service receives one valid hydrated payload, is called twice, returns equivalent data, and invokes its network client zero times.
- An invalid or empty hydrated value falls through once and keeps the current retry or fallback behavior.
- A cache TTL expiry can refresh normally.
- Concurrent first calls remain one-flight where the existing breaker promises that behavior.
- Market, commodity, weather, prediction, risk-score, and theater-posture patterns remain valid and are not double-warmed.

**Verification:** run the new focused service tests, hydration lock tests, bootstrap tests, browser typecheck, and boundary lint.

### U3. Move deferred energy registries to on-demand ownership

**Goal:** remove about 528 KB decoded from the universal slow body while preserving every energy map and panel flow.

**Requirements:** R4, R5, R6, R9.

**Files:**

- `shared/bootstrap-tier-keys.js`
- generated mirror `api/_bootstrap-tier-keys.js` via the repository sync command
- `api/bootstrap.js` if a freshness-specific on-demand cache profile is required
- `shared/pipeline-registry-store.ts`
- `shared/storage-facility-registry-store.ts`
- `src/components/PipelineStatusPanel.ts`
- `src/components/StorageFacilityMapPanel.ts`
- energy map or loader call sites identified during implementation
- `tests/bootstrap.test.mjs`
- `tests/bootstrap-on-demand-cache-budget.test.mts`
- `tests/pipeline-panel-bootstrap.test.mts`
- new startup-cost and late-mount tests modeled on the Canada roads and deferred-panel guards

**Approach:**

1. Reclassify `pipelinesGas`, `pipelinesOil`, and `storageFacilities` from `slow` to `on-demand` in the canonical registry and regenerate the Edge mirror.
2. Make the pipeline and storage registry stores use `ensureHydrated()` after their backward-compatible one-shot hydration check. Keep one in-flight promise and one bounded resolved value per registry.
3. Trace every panel and map-layer consumer. Wire demand at layer enablement, immediate energy-variant startup, or deferred panel approach. Do not depend on a panel constructor side effect to feed a map layer.
4. Compare each key's CDN `s-maxage` with its health freshness budget and publisher cadence. Add the narrow cache profile only when the default is too long.
5. Preserve the panels' background RPC refresh when it adds freshness or detail beyond the registry snapshot. Avoid an immediate duplicate refresh after a successful on-demand result.
6. Add a conditional invariant: these keys cannot ride a universal tier, and a variant can start their request only when its configuration renders the corresponding layer or immediate panel.

**Test Scenarios:**

- Full and happy startup with energy layers off sends no energy registry request.
- Energy startup with a pipeline layer on sends one request per required key and renders the map.
- A deferred pipeline or storage panel sends its request only near mount, survives response-before-mount ordering, and renders complete data.
- An older tier payload containing the keys is consumed without a second request during a rolling deploy.
- On-demand CDN age does not exceed health freshness.
- Desktop persistent fallback and error/retry behavior remain available.

**Verification:** run the focused registry, cache-budget, panel, late-mount, bootstrap, typecheck, API typecheck, and boundary tests.

### U4. Restore fast-tier deadline headroom

**Goal:** make the fast tier materially smaller while preserving true first-paint data and the 1,200 ms deadline.

**Requirements:** R3, R5, R6, R9.

**Files:**

- `shared/bootstrap-tier-keys.js`
- `scripts/publish-bootstrap-tiers.mjs`
- `src/app/data-loader.ts`
- affected service and panel consumers
- a new Bootstrap View helper and key only if the audit proves an immediate consumer needs a compact projection
- `tests/bootstrap.test.mjs`
- deterministic payload-budget tests from U1
- focused consumer tests

**Approach:**

1. Use U1's byte ledger to rank fast keys and trace each key to its earliest consumer across variants, mobile defaults, and desktop defaults.
2. Keep a full key in fast only when a consumer needs the full value before first paint. Demote a key to slow or on-demand when all consumers are deferred or disabled at startup.
3. For an immediate consumer that needs only a subset, create a named Bootstrap View Key at the publisher or seeder boundary. Keep the canonical key intact for RPC, MCP, detail drawers, and later refresh.
4. Start with the largest current contributors: `marketQuotes`, `weatherAlerts`, `canadaAlerts`, `flightDelays`, `commodityQuotes`, `correlationCards`, `earthquakes`, and `forecasts`. The ledger and first-consumer proof decide the change; size alone does not.
5. Add the view or tier consumer before removing the old fast ownership. Preserve rolling-deploy compatibility and current fallback paths.
6. Stop changing keys as soon as the 20% deterministic fast reduction has enough verified deadline headroom. If first-paint requirements make 20% impossible, stop and propose variant-scoped tier objects as a separately reviewed design instead of weakening R3.

**Test Scenarios:**

- Every fast key has a proven pre-paint consumer or a documented compact-view reason.
- Demoted keys do not create a new request in the representative startup flow when their surface is deferred.
- A compact market or alert view contains every field and record required by its immediate consumer and does not change the canonical RPC payload.
- Fast abort fallback still works.
- The deterministic fast fixture is at least 20% smaller and the web deadline constant remains 1,200 ms.

**Verification:** run focused consumer tests, payload budgets, bootstrap tests, browser typecheck, and the request-budget browser test from U5.

### U5. Prove request removal and production acceptance

**Goal:** verify that the changes reduce complete startup transfer without weakening availability, freshness, or fallback behavior.

**Requirements:** R1, R3, R5, R7, R8, R9.

**Files:**

- a focused Playwright request-budget spec, either new or adjacent to `e2e/dashboard-news-request-budget.spec.ts`
- issue evidence tables and operational notes; no mutable pass/fail values in source-controlled CI

**Approach:**

1. Add a representative browser route fixture that completes both tiers, invokes the same hydration paths twice, scrolls deferred panels into range, and records request counts by logical key.
2. Add separate fixtures for fast abort, slow abort, rolling-deploy old-tier data, and energy-variant immediate demand.
3. Run the focused browser spec at mobile and desktop viewports. Assert request counts and rendered data, not just network idleness.
4. After deploy, collect one full daily RUM window with the same sample rate as the U1 baseline. Report traffic-weighted outcomes and byte distributions. Mark thin cells inconclusive.
5. Capture three consecutive scheduled production runs where both tier bodies complete. Compare only fast, slow, and their identified follow-up requests against the completed pre-change reference.
6. Record news digest size separately as a volatile observation. Create a new issue only if matched completed runs show a repeatable digest budget breach.

**Test Scenarios:**

- Complete-tier flow: no duplicate natural, wildfire, earthquake, sanctions, weather, or insights request within TTL.
- Abort flow: fallbacks run, panels recover, and telemetry reports an abort rather than a complete byte sample.
- Deferred energy flow: no startup request on full; one bounded request when demanded; content renders.
- Variant flow: energy retains its startup data, while unrelated variants avoid the transfer.
- Production evidence: baseline and candidate use the same outcome vocabulary, sample rate, daily-cycle duration, and completion filter.

**Verification:** run the focused Playwright matrix. Then use the post-deploy RUM and scheduled DebugBear gates below. Do not close the parent issue from CI alone.

## Verification Contract

Run focused checks first and the required surface gates sequentially:

```bash
node --import tsx --test tests/bootstrap-runtime.test.mts
node --test tests/bootstrap.test.mjs tests/publish-bootstrap-tiers.test.mjs
node --import tsx --test tests/bootstrap-on-demand-cache-budget.test.mts tests/hydration-lock-keys.test.mts tests/pipeline-panel-bootstrap.test.mts
node --import tsx --test tests/bootstrap-hydration-reuse.test.mts tests/bootstrap-transfer-rum.test.mts
node --test tests/bootstrap-payload-budget.test.mjs
npm run sync:bootstrap-tier-keys:check
npx playwright test e2e/bootstrap-request-budget.spec.ts
npm run typecheck
npm run typecheck:api
npm run lint:boundaries
git diff --check
git status --short
```

If the implementation changes proto definitions, run `make generate`; otherwise do not edit `src/generated/`.

### Pull-Request Gates

- All focused request-count, cache, payload-budget, and late-mount tests pass.
- The fast and slow timeout constants are unchanged.
- The canonical and generated tier registries match.
- On-demand cache age is no longer than health freshness for every moved key.
- Browser and API typechecks plus boundary lint pass.
- The browser spec proves both data presence and expected request counts.

### Production Acceptance Gate

- A pre-change and post-change RUM window each cover at least one full daily traffic cycle with the same 10% sampling.
- Traffic-weighted `complete` outcomes are at least 99% for fast and 99% for slow after the change. Every other outcome remains visible as its own rate.
- No well-sampled device or variant cohort regresses by more than 0.5 percentage points. A cell with fewer than 100 samples on either side is `inconclusive`.
- Completed-response byte distributions exclude aborts, network failures, and parse failures.
- Three consecutive scheduled production runs have complete fast and slow bodies and show at least 150 KiB less combined transfer for the two tiers plus identified duplicate follow-ups than the completed pre-change reference.
- No moved dataset is missing from its full, energy, finance, commodity, or desktop consumer.
- Any failure keeps the parent issue open and records which unit owns the repair. It does not trigger a timeout increase.

## Definition of Done

- **U1:** deterministic byte budgets pass; general tier outcome telemetry is deployed; the pre-change daily baseline is recorded; no R2-only client telemetry remains active or falsely documented.
- **U2:** every mandatory observed recurring service retains valid hydration in its owner cache; the bounded audit is complete; repeat-call tests prove zero network calls within TTL.
- **U3:** all three energy registries are on-demand; relevant variants, map layers, panels, late mounts, CDN freshness, and desktop fallback are verified.
- **U4:** the deterministic fast fixture is at least 20% smaller; every remaining fast key has a first-paint justification; no timeout changed.
- **U5:** focused Playwright request budgets pass; the post-deploy window and three completed scheduled runs satisfy the Production Acceptance Gate.
- The parent and child issues link the relevant pull requests and evidence artifacts.
- All abandoned experiments, obsolete comments, old R2-only client telemetry names, and temporary fixtures are removed from the final diffs.
- Final handoff states each terminal condition separately: locally verified, PR ready, merged, deployed, production observed, and acceptance complete.

## Appendix

### Baseline Snapshot

| Request or group | Transfer bytes | Decoded bytes | Duration | Interpretation |
|---|---:|---:|---:|---|
| Fast bootstrap, analysis `86158947` | 149,122 | 766,645 | 1,228 ms | At the 1,200 ms web cutoff; needs headroom |
| Slow bootstrap, analysis `86158947` | 351,175 | 1,937,018 | 2,888 ms | Complete, close to 3,000 ms cutoff |
| Slow bootstrap, analysis `86080141` | 236,456 | 1,218,683 | 3,023 ms | Cutoff-like comparison; not a valid completed-size baseline |
| Natural events fallback | 59,778 | 198,191 | 750 ms | Starts after successful slow completion |
| Wildfires fallback | 18,643 | 167,762 | — | Starts after successful slow completion |
| Sanctions fallback | 5,096 | 18,357 | — | Starts after successful slow completion |
| Weather, earthquakes, insights fallbacks | 35,171 combined | — | — | Mix of fast abort and direct-return reuse gaps |
| Three energy registries inside slow | estimated 93.5 KiB | 527,986 | — | Deferred data paid by every visitor |

These values are diagnostic anchors, not immutable test fixtures. The implementing agent must regenerate deterministic reference fixtures from reviewed repository-owned samples and must use completed production responses for the acceptance comparison.

Contributor guide

Open the contributing guide

Research direction

Start with CONCEPTS.md and trace ensureHydrated() and the existing circuit-breaker warming path. Verify the three energy registries, tier budgets, and completion/byte telemetry against the requirements and acceptance examples. Done means duplicate requests are prevented, deferred data stays out of universal tiers, budgets are enforced, and aborted responses are excluded from completed-payload measurements.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend, observability, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.