Initial Commit
This commit is contained in:
155
openspec/changes/enterprise-browser-packer-browser/proposal.md
Normal file
155
openspec/changes/enterprise-browser-packer-browser/proposal.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# OpenSpec Change: enterprise-browser-packer-browser
|
||||
|
||||
- **Change ID**: enterprise-browser-packer-browser
|
||||
- **Package**: `@suaveplan/packer-browser`
|
||||
- **Category**: browser
|
||||
- **Target path**: `browser/storage/packer-browser`
|
||||
- **Status**: IN PROGRESS
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/packer-browser.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/packer-browser` on the genesis branch at enterprise quality. The package provides the browser-side runtime for `@suaveplan/packer` packed media archives: a Service Worker interceptor, a `PackedResourceManager` with fetch + LRU cache, signed-manifest verification via `@suaveplan/crypto`, and SW lifecycle utilities. React hooks (`usePackedResource` etc.) move to a separate `@suaveplan/react-packer` companion. No `react` runtime dep in this package.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
Fresh build — does not exist on genesis yet.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/packer` (`workspace:^`) | Pack/unpack, archive format, manifest |
|
||||
| `@suaveplan/crypto` (`workspace:^`) | Signed-manifest verification |
|
||||
| `@suaveplan/events` (`workspace:^`) | Resource lifecycle events |
|
||||
| `@suaveplan/error` (`workspace:^`) | Typed errors |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike` |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Structured logging in SW + manager |
|
||||
| `@suaveplan/schema-registry` (`workspace:^`) | Manifest schema registration |
|
||||
| `zod` (`catalog:forms`) | Manifest Zod schema |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Fetch/cache/decode metrics |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
No `react` in runtime deps.
|
||||
|
||||
Dev: `vitest`, `@vitest/coverage-istanbul`, `@playwright/test`, `@suaveplan/testing`.
|
||||
|
||||
---
|
||||
|
||||
## Preserved public API surface
|
||||
|
||||
```ts
|
||||
// Manager (no React dep)
|
||||
class PackedResourceManager {
|
||||
fetch(assetPath: string): Promise<Blob>
|
||||
release(assetPath: string): void
|
||||
getMetrics(): PackedResourceManagerMetrics
|
||||
clear(): void
|
||||
}
|
||||
function createPackedResourceManager(options: ManagerOptions): PackedResourceManager
|
||||
|
||||
// Singleton (backward compat at ./singleton subpath)
|
||||
// import { packedResourceManager } from '@suaveplan/packer-browser/singleton'
|
||||
|
||||
// SW utilities
|
||||
function registerPackedResourceServiceWorker(options?: SwOptions): Promise<ServiceWorkerRegistration>
|
||||
function unregisterPackedResourceServiceWorker(): Promise<boolean>
|
||||
function clearServiceWorkerCache(): Promise<void>
|
||||
function getServiceWorkerCacheStatus(): Promise<SwCacheStatus>
|
||||
|
||||
// Types
|
||||
interface PackedResource { path: string; mimeType: string; size: number; blob: Blob }
|
||||
type PackedResourceType = 'image' | 'audio' | 'font' | 'video' | 'other'
|
||||
```
|
||||
|
||||
Subpath exports: `./manager`, `./service-worker`, `./utils`, `./singleton`.
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. React hooks removed → `@suaveplan/react-packer`
|
||||
|
||||
`usePackedResource`, `usePackedImage`, `usePackedFont`, `usePackedVideo`, `usePackedAudio` all move to a companion package. This package has zero React runtime dep.
|
||||
|
||||
### 2. Signed-manifest verification
|
||||
|
||||
`PackedResourceManager` accepts `manifestSignature` in `ManagerOptions`. Before trusting packed resources, verifies signature with `@suaveplan/crypto` ed25519; throws `PACKED_ARCHIVE_SIGNATURE_INVALID` on failure.
|
||||
|
||||
### 3. Split `PackedResourceManager`
|
||||
|
||||
Into `manager.ts` (orchestration, LRU accounting), `cache.ts` (blob cache, hit/miss, eviction), `loader.ts` (fetch + decode + verify) — each ≤300 lines.
|
||||
|
||||
### 4. Service worker unit test
|
||||
|
||||
`service-worker/service-worker.ts` unit-tested via a `ServiceWorkerGlobalScope` shim; no coverage gap.
|
||||
|
||||
### 5. `createPackedResourceManager` factory
|
||||
|
||||
Replaces module-singleton; explicit factory. Singleton available at `./singleton` backward-compat subpath.
|
||||
|
||||
### 6. Error codes
|
||||
|
||||
`PACKED_RESOURCE_NOT_FOUND`, `SW_REGISTRATION_FAILED`, `PACKED_ARCHIVE_SIGNATURE_INVALID`.
|
||||
|
||||
### 7. OTel metrics
|
||||
|
||||
Counter `packer.resource_fetch_total`, counter `packer.cache_hits_total`, counter `packer.cache_miss_total`, histogram `packer.decode_ms`.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
browser/storage/packer-browser/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
manager/
|
||||
manager/ (manager.ts — LRU orchestrator, manager.test.ts, manager.md)
|
||||
cache/ (cache.ts — blob cache, cache.test.ts, cache.md)
|
||||
loader/ (loader.ts — fetch + decode + manifest verify, loader.test.ts, loader.md)
|
||||
service-worker/
|
||||
service-worker/
|
||||
service-worker.ts
|
||||
service-worker.md
|
||||
service-worker.test.ts (real SW scope shim)
|
||||
index.ts
|
||||
utils/
|
||||
register-service-worker/
|
||||
register-service-worker.ts
|
||||
register-service-worker.md
|
||||
register-service-worker.test.ts
|
||||
index.ts
|
||||
singleton/
|
||||
index.ts (re-exports packedResourceManager singleton)
|
||||
types/
|
||||
types/
|
||||
types.ts
|
||||
types.md
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
e2e/
|
||||
packer-browser.spec.ts (Chromium/Firefox/WebKit SW interception)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: any browser app consuming packed media archives; `@suaveplan/react-packer` (new companion).
|
||||
|
||||
**Breaking changes**: `usePackedResource` hooks removed from this package (relocated to companion).
|
||||
|
||||
**Prerequisites**: `@suaveplan/packer`, `@suaveplan/crypto`, `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/logger` on Verdaccio.
|
||||
123
openspec/changes/enterprise-browser-packer-browser/tasks.md
Normal file
123
openspec/changes/enterprise-browser-packer-browser/tasks.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Tasks: enterprise-browser-packer-browser
|
||||
|
||||
**Change ID**: enterprise-browser-packer-browser
|
||||
**Package**: `@suaveplan/packer-browser`
|
||||
**Target path**: `browser/storage/packer-browser`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/packer`, `@suaveplan/crypto`, `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/logger`
|
||||
|
||||
---
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create directory tree per `proposal.md`.
|
||||
- [ ] `package.json` v0.0.0, `"sideEffects": false`.
|
||||
- `dependencies`: `@suaveplan/packer workspace:^`, `@suaveplan/crypto workspace:^`, `@suaveplan/events workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/schema-registry workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `@playwright/test catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- Subpath exports: `"."`, `"./manager"`, `"./service-worker"`, `"./utils"`, `"./singleton"`
|
||||
- [ ] `tsconfig.json`, `vitest.config.ts` (pool: forks, `fileParallelism: false`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes
|
||||
|
||||
- [ ] `PACKED_RESOURCE_NOT_FOUND`, `SW_REGISTRATION_FAILED`, `PACKED_ARCHIVE_SIGNATURE_INVALID`.
|
||||
- [ ] `registerPackerBrowserErrorCodes()` — call eagerly in `src/index.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Types (`src/types/types/`)
|
||||
|
||||
- [ ] `PackedResource { path: string; mimeType: string; size: number; blob: Blob }`.
|
||||
- [ ] `PackedResourceType = 'image' | 'audio' | 'font' | 'video' | 'other'`.
|
||||
- [ ] `ManagerOptions`, `SwOptions`, `SwCacheStatus`, `PackedResourceManagerMetrics`.
|
||||
- [ ] Write `types.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Cache module (`src/manager/cache/`)
|
||||
|
||||
- [ ] `cache.ts` — LRU blob cache: `get(path)`, `set(path, blob)`, `evict()`, `clear()`, `metrics()`.
|
||||
- [ ] Configurable `maxEntries` and `maxBytes`; eviction on exceed.
|
||||
- [ ] Write `cache.test.ts` (CRUD, LRU eviction order, byte-limit eviction). Write `cache.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Loader module (`src/manager/loader/`)
|
||||
|
||||
- [ ] `loader.ts` — fetch archive URL, decode via `@suaveplan/packer`, verify manifest signature when `manifestSignature` provided.
|
||||
- [ ] On invalid signature throw `PACKED_ARCHIVE_SIGNATURE_INVALID` via `@suaveplan/crypto` ed25519 verify.
|
||||
- [ ] Write `loader.test.ts` (fetch success, decode, signature valid, signature invalid). Write `loader.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Manager module (`src/manager/manager/`)
|
||||
|
||||
- [ ] `manager.ts` — LRU orchestrator composing `cache.ts` + `loader.ts`.
|
||||
- [ ] `fetch(assetPath): Promise<Blob>` — cache hit fast path; cache miss → loader → cache insert.
|
||||
- [ ] `release(assetPath)`, `clear()`, `getMetrics(): PackedResourceManagerMetrics`.
|
||||
- [ ] Wire OTel counters: `packer.resource_fetch_total`, `packer.cache_hits_total`, `packer.cache_miss_total`; histogram `packer.decode_ms`.
|
||||
- [ ] Write `manager.test.ts` (fetch hit, fetch miss, release, clear, metrics). Write `manager.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. `createPackedResourceManager` factory (`src/manager/`)
|
||||
|
||||
- [ ] `index.ts` re-exporting `createPackedResourceManager(options: ManagerOptions): PackedResourceManager`.
|
||||
- [ ] Factory validates options with Zod; throws typed error on invalid config.
|
||||
|
||||
---
|
||||
|
||||
## 8. Service Worker (`src/service-worker/service-worker/`)
|
||||
|
||||
- [ ] `service-worker.ts` — `ServiceWorkerGlobalScope` handler: intercept fetch, match against packed archive cache, respond or pass through.
|
||||
- [ ] Write `service-worker.test.ts` via a `ServiceWorkerGlobalScope` shim (no `vi.stubGlobal`).
|
||||
- [ ] Write `service-worker.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. SW utilities (`src/utils/register-service-worker/`)
|
||||
|
||||
- [ ] `register-service-worker.ts` — `registerPackedResourceServiceWorker(options?)`, `unregisterPackedResourceServiceWorker()`, `clearServiceWorkerCache()`, `getServiceWorkerCacheStatus()`.
|
||||
- [ ] Throw `SW_REGISTRATION_FAILED` on registration failure.
|
||||
- [ ] Write `register-service-worker.test.ts`. Write `register-service-worker.md`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Singleton subpath (`src/singleton/`)
|
||||
|
||||
- [ ] `index.ts` — `export const packedResourceManager = createPackedResourceManager(defaultOptions)` for backward compat.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integration test (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Fetch multiple assets from a packed archive.
|
||||
- [ ] Verify LRU eviction under quota pressure.
|
||||
- [ ] Verify signature rejection path.
|
||||
|
||||
---
|
||||
|
||||
## 12. Playwright E2E (`e2e/packer-browser.spec.ts`)
|
||||
|
||||
- [ ] Chromium, Firefox, WebKit browser contexts.
|
||||
- [ ] Assert SW intercepts packed asset requests and serves blobs.
|
||||
- [ ] Assert SW cache invalidation after `clearServiceWorkerCache()`.
|
||||
|
||||
---
|
||||
|
||||
## 13. Docs + verify
|
||||
|
||||
- [ ] Write `.md` per module (≥200 words, API reference, examples).
|
||||
- [ ] Write `README.md` with subpath table, manifest-verification guide, SW setup.
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/packer-browser` — green.
|
||||
- [ ] `bunx turbo test:coverage` — 100%.
|
||||
- [ ] No `@ts-expect-error` in `src/` or tests. No `vi.stubGlobal`.
|
||||
- [ ] Playwright E2E green on Chromium, WebKit, Firefox.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/packer-browser`.
|
||||
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-packer-browser/`.
|
||||
Reference in New Issue
Block a user