184 lines
12 KiB
Markdown
184 lines
12 KiB
Markdown
# 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).
|