# Proposal: enterprise-streams-metadata - **Change ID**: enterprise-streams-metadata - **Package**: `@suaveplan/streams-metadata` - **Status**: PROPOSED - **Target path**: `packages/foundation/flow/streams-metadata` - **Spec delta**: ./specs/streams-metadata/spec.md - **Upstream source**: `/mnt/s/web/suaveplan/packages/universal/streams-metadata` - **Upstream enterprise audit**: `/mnt/s/web/suaveplan/enterprise/streams-metadata.md` ## Why `@suaveplan/streams-metadata` provides a composable, stream-oriented pipeline for extracting MIME-type and EXIF/image metadata from `Uint8Array` payloads. Genesis already ships the prerequisites (`@suaveplan/streams`, `@suaveplan/mime`, `@suaveplan/exif`, `@suaveplan/events`) at 100/100/100/100 coverage — this change ports the metadata package on top of them at the same quality bar. This proposal supersedes an earlier draft in this folder that designed a side-channel `Promise` API and folded in audio metadata. The upstream source (which shipped to consumers) implements three `BaseTransform` subclasses with a typed event surface; migration parity (`openspec/AGENTS.md` §0a) mandates shipping that contract, not a redesign. ## What A fresh universal-tier package providing: 1. `MimeTransform` — magic-bytes detection (priority) → extension fallback → `application/octet-stream`. 2. `ExifTransform` — `ImageMetadata` via `@suaveplan/exif::readMetadata`. 3. `MetadataTransform` — composing wrapper running MIME then EXIF, forwarding child events, catching errors, emitting `metadata:complete` / `metadata:error`. 4. Typed event surface via `BaseTransform.on/emit` (genesis `@suaveplan/events` no longer exports an `EventEmitter` class). 5. Typed `StreamsMetadataError` with `defineCodes("STREAMS_METADATA", …)` registry. 6. Subpath exports per feature directory (CLAUDE.md §6). ## Parity Checklist | Capability | Upstream | Migrated | Notes | |---|---|---|---| | `MimeTransform` (enrich / passthrough) | YES | YES | | | `ExifTransform` (enrich / passthrough, `read` pass-through) | YES | YES | | | `MetadataTransform` wrapper | YES | YES | | | Typed events (`mime:detected`, `exif:detected`, `metadata:complete`, `metadata:error`) | YES | UP | Migrated from `EventEmitter` field to `BaseTransform.on/emit`. Genesis `@suaveplan/events` has no `EventEmitter` class. Observable surface unchanged. | | `EnrichedPayload` / `MetadataPayload` / `MimeDetectionResult` / `MetadataMode` | YES | YES | | | Detection precedence (magic → extension → fallback) | YES | YES | | | `ExifTransform` raise on unknown format | YES | UP | Upstream threw plain `Error`. Migration lets `@suaveplan/exif::readMetadata` throw `ExifError(UNSUPPORTED_FORMAT)`, re-wrapped as `ExifFailedError(STREAMS_METADATA.EXIF_FAILED)` with `cause` preserved. | | Child-event forwarding through the wrapper | YES | YES | | | `metadata:error` + original-input return on failure | YES | YES | | | Stable error codes (`STREAMS_METADATA.*`) | NO | UP | Upstream had no registry. Migration adds `STREAMS_METADATA_CODES` via `defineCodes`. | | Subpath exports per feature dir | NO | UP | Upstream exported only `.`. Migration adds per-feature subpaths for tree-shaking. | | `@suaveplan/testing/runner` test imports | NO | UP | Upstream imported from `vitest`. Migration uses canonical wrapper per CLAUDE.md §11. | | Sibling `.md` per `.ts` with all six sections | NO | UP | Upstream shipped partial docs. Migration ships full coverage. | | 100/100/100/100 coverage gate | NO | UP | Upstream did not enforce 100%. Migration enforces it. | ## Impact ### Affected dependents None inside genesis at change time — this package is a fresh universal-tier leaf in this monorepo. Future consumers (`@suaveplan/api-plugin-file-upload`) will consume after archive. ### Risks - Genesis `@suaveplan/events` does not export `EventEmitter`. The migration switches to `BaseTransform`'s built-in `on`/`emit`, changing the public observation channel from `transform.events.on(...)` to `transform.on(...)`. This is `UP` (upgraded) not `NO` (dropped) because the observable surface is unchanged. - The genesis `@suaveplan/mime` dist tree-shakes its signature-registration side effect, so external consumers (including this package) get an empty registry at runtime. The migration installs explicit JPEG / PNG signatures via `registerSignature` in `src/mime/mime.ts`'s module load so detection works end-to-end without depending on an upstream fix. This is a temporary workaround — a future change will fix the mime dist build so the registration is universal. ## Out of scope - Streaming-mode (chunked) MIME / EXIF detection — both transforms operate on a fully buffered `Uint8Array` exactly as upstream did. - Audio metadata extraction — out of scope; reserved for a follow-up change. - Side-channel metadata promises / 64 KiB byte caps — not part of the upstream contract. - Adding new media format support beyond what `@suaveplan/mime` and `@suaveplan/exif` already provide.