Files
suaveplan/openspec/archive/2026-05-19-add-packer-browser-core/proposal.md
2026-05-27 23:29:53 +01:00

12 KiB
Raw Blame History

add-packer-browser-core — Cluster A migration of @suaveplan/packer-browser

Package: @suaveplan/packer-browser Status: PROPOSED Type: new-package Design: not applicable (Cluster A core; no separate design.md — architecture is captured inline in tasks.md and the spec delta). Spec delta: ./specs/packer-browser/spec.md

Why

@suaveplan/packer-browser is the browser-tier runtime for whole-file packed archives produced by @suaveplan/packer. The upstream form (audit: ../suaveplan/enterprise/packer-browser.md lines 1–115) carries a production-grade PackedResourceManager (LRU + ref-count + Blob-URL bookkeeping), a Service Worker interceptor, and lifecycle helpers — but violates four genesis quality bars: (1) PackedResourceManager.ts is 318 LOC (over the 300-line hard cap), (2) the Service Worker is untested, (3) the package ships React hooks as a runtime dependency, and (4) it has no archive signature verification, no OTel metrics, no injected Clock/Rng, no MessageResolver-keyed errors, no AbortSignal propagation, and no @suaveplan/retry on the IO source-pull path. Cluster A lands the production-quality core that fixes every cross-cutting gap and the §3 audit items; Clusters B and C land §4 enhancements (progressive streaming, range-request SW, encrypted archives, versioned URLs, BackgroundFetch, diagnostics overlay).

The package is NEW on genesis — STATUS.md marks it as pending under the bitpacker section (line 129). This change also relocates it under data: in scripts/folder-reorg/assignment.yml between packer and pagination, mirroring @suaveplan/packer's home (the upstream bitpacker grouping is historical and no longer matches its dep graph).

What changes

This change adds a new package; every requirement is ## ADDED. The public surface is:

  • ADDED: PackedResourceManager (class) — lifecycle façade over the cache
    • loader; emits typed EventEmitter events for load / cacheHit / cacheMiss / cacheEvict / error.
  • ADDED: createPackedResourceManager(options?) — explicit factory; the default . export does NOT construct a singleton at import time.
  • ADDED: packedResourceManager — module-singleton, hosted behind the /singleton subpath export so the default surface stays import-cost-free.
  • ADDED: PackedResourceCache + PackedResourceLoader — the two collaborators behind the manager. Available via /manager subpath for consumers that need surgical imports.
  • ADDED: runPackedResourceServiceWorker(scope) + handlers for install / activate / fetch / message, exposed via /service-worker subpath along with the hand-rolled ServiceWorkerGlobalScope shim (createServiceWorkerShim) used by the unit-test harness.
  • ADDED: registerPackedResourceServiceWorker / unregisterPackedResourceServiceWorker / clearServiceWorkerCache / getServiceWorkerCacheStatus, via /utils subpath.
  • ADDED: verifyArchiveSignature(manifestBytes, signature, publicKey) — Ed25519 verification gate run before any packed resource is unpacked.
  • ADDED: PACKER_BROWSER_CODES + PackerBrowserError (extends SuaveplanError) — ≥6 stable codes: RESOURCE_NOT_FOUND, SW_REGISTRATION_FAILED, ARCHIVE_SIGNATURE_INVALID, UNSUPPORTED_RESOURCE_TYPE, ABORTED, LOAD_FAILED.
  • ADDED: createPackerBrowserObservability + PackerBrowserObservability — OTel meter/tracer bundle exposing packed_resource_fetch_total, packed_resource_cache_hits_total, packed_resource_cache_miss_total counters and packed_resource_decode_ms histogram.
  • ADDED: PACKER_BROWSER_MESSAGES + defaultPackerBrowserResolver — English i18n catalog keyed packer-browser.error.<CODE>.<scenario>.
  • ADDED: resolveRuntime + defaultClock / defaultRng — injectable platform primitives (mirrors @suaveplan/packer/runtime).

Out of scope

  • React hooks (usePackedResource, usePackedAudio, usePackedFont, usePackedImage, usePackedVideo) — these belong in a separate @suaveplan/react-packer companion (assignment task #5 in the upstream enterprise audit). Adding React as a runtime dep would force the browser-tier package onto every consumer; the audit explicitly recommends the split.
  • Progressive streaming unpack (§4.1) — Cluster B.
  • Range-request SW support (§4.2) — Cluster B.
  • Adaptive cache sizing via navigator.storage.estimate() (§4.3) — Cluster B.
  • Encrypted-archive pluggable keyring (§4.4) — Cluster B. (Basic AEAD decryption via the existing @suaveplan/crypto/@suaveplan/packer surface is supported on the loader; the pluggable keyring is what Cluster B adds.)
  • <link rel="preload"> generation (§4.5) — Cluster C.
  • Content-addressed versioned URLs + invalidation (§4.6) — Cluster C.
  • Multi-origin SW helper (§4.7) — Cluster C.
  • BackgroundFetch API integration (§4.8) — Cluster C.
  • Diagnostics dev overlay (§4.9) — Cluster C.
  • Subpath splits beyond ., /singleton, /manager, /service-worker, /utils (§4.10) — Cluster C.

Source mapping

This change derives from ../suaveplan/enterprise/packer-browser.md.

Requirement Source lines Notes
PACKER-BROWSER-1 — Manager split (lifecycle / cache / loader) L20-L22, L35-L36, L45-L45 Splits the 318-LOC PackedResourceManager.ts into manager.ts, cache.ts, loader.ts, each ≤ 300 LOC.
PACKER-BROWSER-2 — Service Worker is unit-tested via a real SW-scope shim L29-L30, L40-L41, L46-L46 Hand-rolled createServiceWorkerShim(); no vi.mock.
PACKER-BROWSER-3 — Archive signature verification (Ed25519) before unpack L38-L39, L48-L48 Run via @suaveplan/crypto ed25519.verify before the loader hands bytes to Unpacker.
PACKER-BROWSER-4 — OTel metrics emitted from production paths L37-L38, L49-L49 Counters + histogram on the loader/cache; asserted via real in-memory MeterProvider.
PACKER-BROWSER-5 — Factory + singleton split (/singleton subpath) L36-L37, L51-L51 createPackedResourceManager(opts) plus a ./singleton module-side export.
PACKER-BROWSER-6 — Error hierarchy via @suaveplan/error L62-L63 PACKER_BROWSER_CODES ≥ 6 codes; PackerBrowserError extends SuaveplanError.
PACKER-BROWSER-7 — AbortSignal propagation on every async entrypoint (cross-cutting; mirrors PACKER-24) All async public APIs accept signal? and check at every yield point.
PACKER-BROWSER-8 — i18n MessageResolver keys (no hard-coded English) (cross-cutting; mirrors PACKER-25 i18n cluster) PACKER_BROWSER_MESSAGES catalog + defaultPackerBrowserResolver.
PACKER-BROWSER-9 — Injected Clock/Rng (no Date.now/Math.random outside runtime defaults) (cross-cutting; mirrors PACKER-25) resolveRuntime + defaultClock/defaultRng.
PACKER-BROWSER-10 — @suaveplan/retry on IO source-pull path (cross-cutting; mirrors PACKER-22) Loader's fetch step routes through withRetry with single-attempt default.
PACKER-BROWSER-11 — @suaveplan/feature-flags provider injected (cross-cutting; reserved for Cluster B/C) No-op default; flags slot present so Cluster B can opt in without API churn.
PACKER-BROWSER-12 — Subpath exports ./manager, ./service-worker, ./utils, ./singleton L78-L78, L90-L91 Tree-shakeable surface for the §3 minimum.

Requirements with no source line

Requirement Justification
PACKER-BROWSER-7 / -8 / -9 / -10 / -11 Cross-cutting genesis baselines (CLAUDE.md §0a parity rules — every migrated package must adopt the runtime/observability/i18n/retry/flag scaffolding regardless of whether the audit explicitly called it out).

Verification approach

Every requirement carries one or more **Verified by:** entries pointing at concrete it(...) titles in the listed test files. Tests are co-located with implementation (src/<feature>/<feature>.test.ts).

  • src/manager/manager.test.ts — covers PACKER-BROWSER-1, -5, -7, -11.
  • src/cache/cache.test.ts — covers PACKER-BROWSER-1, -4, -9.
  • src/loader/loader.test.ts — covers PACKER-BROWSER-1, -3, -4, -7, -10.
  • src/service-worker/service-worker.test.ts — covers PACKER-BROWSER-2.
  • src/service-worker/sw-shim.test.ts — covers PACKER-BROWSER-2.
  • src/signature/signature.test.ts — covers PACKER-BROWSER-3.
  • src/error-codes.test.ts — covers PACKER-BROWSER-6.
  • src/i18n/messages.test.ts — covers PACKER-BROWSER-8.
  • src/runtime/runtime.test.ts — covers PACKER-BROWSER-9.
  • src/observability/observability.test.ts — covers PACKER-BROWSER-4.
  • src/utils/register-service-worker.test.ts — covers PACKER-BROWSER-2.
  • src/singleton.test.ts — covers PACKER-BROWSER-5.
  • src/flags/flags.test.ts — covers PACKER-BROWSER-11.
  • tests/e2e/sw.spec.ts — Playwright sweep proving SW registration + packed URL interception in a real Chromium tab.

Parity Checklist

Capability Upstream Migrated Notes
PackedResourceManager class (LRU, refCount, Blob bookkeeping) ✅ ⬆️ Migrated; split into manager + cache + loader; now factory-constructed.
Module-singleton packedResourceManager ✅ ⬆️ Migrated to /singleton subpath; default . export no longer auto-constructs.
Service Worker interceptor (install / activate / fetch / message) ✅ ⬆️ Migrated; now unit-tested via hand-rolled ServiceWorkerGlobalScope shim.
Lifecycle helpers (register/unregister/clearCache/getCacheStatus) ✅ ✅ Migrated 1:1 under /utils.
React hooks (usePackedResource*) ✅ 🚫 Out of scope per audit recommendation: relocated to companion @suaveplan/react-packer package (assignment.md task #5). Excluding React from the runtime surface is the explicit audit ask.
Archive signature verification 🚫 ⬆️ Added: verifyArchiveSignature via @suaveplan/crypto's ed25519.verify. Audit §3 ask.
OTel metrics (fetch/hit/miss/decode) 🚫 ⬆️ Added: real MeterProvider-asserted counters + histogram. Audit §3 ask.
MessageResolver-keyed errors 🚫 ⬆️ Added: PACKER_BROWSER_MESSAGES catalog. Cross-cutting genesis baseline.
Injected Clock / Rng 🚫 ⬆️ Added: resolveRuntime mirrors packer's pattern.
AbortSignal on every async entrypoint 🚫 ⬆️ Added: load / preload / verify / SW fetch all accept and check signal.
@suaveplan/retry on IO source-pull 🚫 ⬆️ Added: loader routes fetch through withRetry.
@suaveplan/feature-flags provider 🚫 ⬆️ Added: no-op default; reserved for Cluster B/C feature gates.
Progressive streaming unpack 🚫 🚫 Cluster B (audit §4.1).
Range-request SW support 🚫 🚫 Cluster B (audit §4.2).
Adaptive cache sizing 🚫 🚫 Cluster B (audit §4.3).
Encrypted-archive pluggable keyring 🚫 🚫 Cluster B (audit §4.4).
<link rel="preload"> hints 🚫 🚫 Cluster C (audit §4.5).
Versioned content-addressed URLs 🚫 🚫 Cluster C (audit §4.6).
Multi-origin SW helper 🚫 🚫 Cluster C (audit §4.7).
BackgroundFetch integration 🚫 🚫 Cluster C (audit §4.8).
Diagnostics dev overlay 🚫 🚫 Cluster C (audit §4.9).
Subpath splits beyond minimum five 🚫 🚫 Cluster C (audit §4.10).

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/packer-browser to enumerate. The package is NEW on genesis with zero existing consumers:

  • No dependents (verified by grep -rln "@suaveplan/packer-browser" packages/ apps/).

Migration

Not applicable — no genesis consumer code exists to migrate. Future React consumers should target the planned @suaveplan/react-packer companion package (out of scope here).