diff --git a/.github/ISSUE_SPECS/010-move-the-codec-to-stable-effect-4-0-x.md b/.github/ISSUE_SPECS/010-move-the-codec-to-stable-effect-4-0-x.md new file mode 100644 index 0000000..66a3980 --- /dev/null +++ b/.github/ISSUE_SPECS/010-move-the-codec-to-stable-effect-4-0-x.md @@ -0,0 +1,29 @@ +# Move the codec to stable Effect 4.0.x (0.2.0) + +GitHub: https://github.com/misofm/codec/issues/10 + +## Objective and immutable scope + +Move `@misofm/codec` from Effect release candidates (peer `4.0.0-rc.112 || 4.0.0-rc.115`) to stable Effect `4.0.x` so `misofm/app` and `@misofm/engine-web-adapter` can share one Effect 4.0.4 copy. Release as 0.2.0. Preserve the public API (built `dist` identical to 0.1.1), the Wasm assets (encoder `90da75c7…`, decoder `70caf381…`), bounds, cancellation, and backpressure. No source, ABI, or asset change; no adapter, app, or Engine work. + +Why: `engine-web-adapter@0.5.13` depended on `effect@4.0.0-rc.112`. When the application shares one Effect 4.0.x copy with the codec (deduplicated install, for example bun's loose peers), the adapter's rc.112 runtime runs a codec `Layer` built by that copy and fails with `Cannot read properties of undefined (reading 'scheduler')`. Unit tests did not catch it because they resolve one Effect copy. + +Base: origin/main `10abab98191b878482a02c0d8e9a2572c8eb0958` (codec 0.1.1). Branch `effect-4.0.4`, PR https://github.com/misofm/codec/pull/9. Effect guidance: [skills revision 0507930](https://github.com/unconfirmedlabs/skills/tree/0507930b6dd13f39739aba40b86a6afb63d32e09/effect-ts) (`references/migration.md`, "Effect v4 release candidates to 4.0.x"). + +## Implementation contract + +- `package.json`: version 0.2.0, peer `effect >=4.0.3 <4.1`, dev dependency `effect` pinned exactly to 4.0.4 (the codec imports only `effect`; there is no `@effect/*` package), `CHANGELOG.md` in `files`. Regenerate `bun.lock`. +- `scripts/pack-check.mjs` runs the isolated packed-consumer matrix on Effect 4.0.3 and 4.0.4 (Bun, Node, Chromium, Firefox, WebKit) and requires `CHANGELOG.md` in the tarball. +- `CHANGELOG.md` records the behaviour audit and the consumer migration note. README and `.github/RELEASING.md` name the new matrix. +- Historical evidence (`docs/evidence/packed-consumers.json`, `.github/RELEASE_EVIDENCE/003`, `006`) is not rewritten. + +## Evidence (candidate, not published) + +- Compiles unchanged against 4.0.4; the built `.js`, `.d.ts`, and maps are byte-identical to the 0.1.1 output. +- Differential script across {0.1.1, 0.2.0} × {rc.115, 4.0.4}: identical error shapes (message, JSON, `Cause.pretty`, Schema encoding), encoded FLAC and decoded PCM hashes, exit reasons, source-error identity, interruption while waiting for input, early take, timeout on a stalled source, and encode interruption without late writes. One measured exception: `JSON.stringify` of a `FlacDecodeError` whose `cause` is a `SchemaError` is now compact (Effect #8895); the only producer is an ABI-violation path (see `CHANGELOG.md`). +- Local: `bun run check` (40 pass, 5 native-only skips), pinned-native `bun run test:integration` (5 pass), `bun run build:wasm:verify` (both assets reproduce), `bun run pack:check` on 4.0.3 and 4.0.4. `npm install --strict-peer-deps` plus `npm ls` valid on 4.0.4 and 4.0.3; `4.0.0-rc.115` is rejected with `ERESOLVE`. +- Repro of the original failure and its absence with codec 0.2.0 plus adapter 0.6.0: see the adapter PR https://github.com/misofm/engine-web-adapter/pull/130. + +## Release contract and blockers + +Release follows `.github/RELEASING.md`: merge a reviewed, green PR; run `npm-publish.yml` on `main` with `mode=publish`, the exact 40-character merge SHA, and `expected_version=0.2.0`; create the `v0.2.0` release/tag at that commit; record the run URL, registry integrity, and decoder hash here and in `.github/RELEASE_EVIDENCE/010/registry-release.json`. Blockers: the adapter PR installs this codec from the throwaway branch `effect-4.0.4-ci-dist` and must be flipped to the published 0.2.0 after release; delete that branch afterwards. Publication has not been performed. diff --git a/.github/RELEASING.md b/.github/RELEASING.md index 514e035..52d4387 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -86,5 +86,5 @@ The existing consumer script accepts these optional environment variables: A registry check uses `CODEC_PACK_SOURCE=@misofm/codec@` and the expected hash from the release qualification. It checks the actual registry tarball in -isolated installations across Effect rc.112/rc.115, Bun, Node, Chromium, Firefox, +isolated installations across Effect 4.0.3/4.0.4, Bun, Node, Chromium, Firefox, and WebKit. Keep receipts in the issue evidence; do not commit the tarball. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f3c4aef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +All notable changes to `@misofm/codec`, newest first. + +## 0.2.0 + +Moves the package from Effect release candidates (`4.0.0-rc.112 || 4.0.0-rc.115`) +to stable Effect `4.0.x`: developed and tested on `4.0.4`, peer floor `4.0.3`. +The public API, the Wasm assets, and the built JavaScript are unchanged. The +`dist/` output compiled against Effect 4.0.4 is byte-identical to the 0.1.1 output +(every `.js`, `.d.ts`, and map), and no source file changed. + +### Changed (breaking) + +- **Peer `effect` is now `>=4.0.3 <4.1`** (was `4.0.0-rc.112 || 4.0.0-rc.115`). + Release candidates are no longer supported; stay on 0.1.x for them. + +### Behaviour changes from Effect 4.0.x + +The codec uses `Stream.transformPullBracket`, `Stream.rechunk`, `Stream.runForEach`, +`Pull.matchEffect`, `Cause.done`, `Scope.addFinalizer`, `Effect.acquireRelease`, +`Effect.scoped`, `Effect.try`/`tryPromise`, `Effect.forEach`, `Effect.yieldNow`, +`Schema.decodeUnknownEffect`, `Schema.TaggedError`, `Schema.Defect`, `Layer.effect`, +and `Context.Service`. It uses no `partition`, `Effect.retry`, `Schedule`, +`Effect.timeout`, `Worker`, or `Effect.async`/`callback`, so the partition-order, +retry, schedule, and TimeoutError-text changes do not apply. + +A differential run of the same script against the 0.1.1 package on `effect@4.0.0-rc.115` +and the 0.2.0 package on `effect@4.0.4` (also 0.1.1 on 4.0.4 and 0.2.0 on rc.115) +produced identical output for: + +- the `message`, `name`, JSON, `Cause.pretty`, and Schema-encoded forms of + `FlacDecodeError`, `FlacEncodeError`, and `PcmInputError` (with and without a plain + `Error` as `cause`, and with `bigint` fields); +- the encoded FLAC bytes, STREAMINFO MD5, decoded PCM bytes, and `Complete` event; +- the exit reasons for truncated input, a failing source (the source's error object + is still returned by identity), invalid options, and invalid PCM blocks; +- interruption while the decoder waits for input (one `Interrupt` reason, source + finalizer run once), early `Stream.take` (no `Complete`, source finalizer run once), + `Effect.timeout` on a stalled source, and interruption of an encode with a pending + source (one `Interrupt` reason, no late sink writes). + +One difference is known and was measured: `JSON.stringify` of a `FlacDecodeError` whose +`cause` is an Effect `SchemaError` now yields the compact `{ "_tag": "SchemaError", +"message": ... }` object (Effect 4.0.3, #8895) instead of a dump of the schema AST. Its +Schema-encoded form and `Cause.pretty` output are unchanged. The codec sets such a cause +in one place only: when the decoder Wasm reports a PCM format that `PcmFormat` rejects +(`reason: "wasm-abi"`), an ABI violation that the pinned decoder asset does not produce, +so the path is effectively unreachable. + +Effect 4.0.x changes that touch this code, and why they are unobservable here: + +- `Scope.close` finalizers run uninterruptibly (Effect rc.118). The decoder release + finalizer and the encoder `acquireRelease` release are synchronous and bounded, so + interruption could not abandon them before either. +- A pending interruption is kept when an uninterruptible effect fails, and typed + failures are dropped when interruption skips recovery handlers (Effect 4.0.1, #8652). + The Wasm calls are synchronous and the codec has no uninterruptible region of its own, + so a decode or encode is still interrupted at the next pull, input wait, or sink write. + +### Migrating from 0.1.x + +1. Move your whole Effect release train to stable together and pin it to one `4.0.x` + version, for example `"effect": "4.0.4"` plus every `@effect/*` package you use at + `4.0.4`. A mixed release-candidate and stable install fails to resolve `effect/*` + module paths, and two copies of `effect` give the shared `Context.Service` tags, + tagged errors, and `Layer` internals different identities. In particular, a + Layer built by one copy's runtime and constructed by another copy fails with + `Cannot read properties of undefined (reading 'scheduler')`: this happens when a + dependency such as `@misofm/engine-web-adapter@0.5.x` still pins + `effect@4.0.0-rc.112` while the app and `@misofm/codec` use 4.0.x. Upgrade every + `@misofm/*` package that depends on `effect` in the same change. +2. Nothing else changes in code that imports `@misofm/codec`. diff --git a/README.md b/README.md index 0129fce..c929652 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,16 @@ files, media pipelines, delivery chunks, manifests, storage, and publication. See [package boundaries](docs/architecture.md) and the [engine web adapter integration contract](docs/adapter-integration.md). -Install the package with a supported Effect peer: +Install the package with a supported stable Effect peer (`>=4.0.3 <4.1`), and +pin the whole Effect release train you use to one exact version: ```sh -bun add @misofm/codec effect@4.0.0-rc.115 +bun add @misofm/codec effect@4.0.4 ``` +See the [changelog](CHANGELOG.md) for the migration from 0.1.x (Effect release +candidates). + ## Encoding `encodeFlac(source, sink, options)` accepts an Effect stream of signed, @@ -138,8 +142,8 @@ repository and uses the resulting CLI only as a test oracle: CODEC_NATIVE_FLAC=$(scripts/build-native-flac.sh) bun run test:integration ``` -The supported peer matrix is `effect@4.0.0-rc.112` and `4.0.0-rc.115`; development -pins rc.115. Packed consumer checks execute real incremental encoding, decoding, +The supported peer range is `effect >=4.0.3 <4.1`; the packed-consumer matrix +runs `effect@4.0.3` and `4.0.4`, and development pins `4.0.4`. Packed consumer checks execute real incremental encoding, decoding, round trips, cancellation, and typed truncation failures on Bun 1.4.2, Node 22.23.2, and Chromium, Firefox, and WebKit workers. Consumer examples are compiled from an isolated installed tarball, without workspace aliases. diff --git a/bun.lock b/bun.lock index 0926a2d..51d87d7 100644 --- a/bun.lock +++ b/bun.lock @@ -7,13 +7,13 @@ "devDependencies": { "@types/bun": "1.3.9", "@types/node": "22.20.2", - "effect": "4.0.0-rc.115", + "effect": "4.0.4", "playwright": "1.62.1", "prettier": "3.6.2", "typescript": "5.9.3", }, "peerDependencies": { - "effect": "4.0.0-rc.112 || 4.0.0-rc.115", + "effect": ">=4.0.3 <4.1", }, }, }, @@ -24,7 +24,7 @@ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], - "effect": ["effect@4.0.0-rc.115", "", {}, "sha512-ogYulZ5ffeOzrJqQrG0XOkDO5lKn2s9KNHhoxJy/6wKUkF0i12dlVXWA9TgsgQ+zV/JzizyG0+XMPepZEQX6vw=="], + "effect": ["effect@4.0.4", "", {}, "sha512-Sr50OeVSC3M5UkoUYUhFf3EULryhGaZZUf6OzDHTvaznOw7HRlxOKdF3/4nvQXHL22StOyMG3Cvo8Bt7xUp+Ng=="], "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], diff --git a/package.json b/package.json index b159eb2..4baa41f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@misofm/codec", - "version": "0.1.1", + "version": "0.2.0", "description": "Effect-based audio codec primitives backed by pinned libFLAC WebAssembly", "license": "Apache-2.0", "repository": { @@ -35,6 +35,7 @@ "wasm/*.wasm", "wasm/*.manifest.json", "README.md", + "CHANGELOG.md", "docs/*.md", "LICENSE", "THIRD_PARTY_NOTICES.md", @@ -54,12 +55,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p benchmark/tsconfig.json --noEmit" }, "peerDependencies": { - "effect": "4.0.0-rc.112 || 4.0.0-rc.115" + "effect": ">=4.0.3 <4.1" }, "devDependencies": { "@types/bun": "1.3.9", "@types/node": "22.20.2", - "effect": "4.0.0-rc.115", + "effect": "4.0.4", "playwright": "1.62.1", "prettier": "3.6.2", "typescript": "5.9.3" diff --git a/scripts/pack-check.mjs b/scripts/pack-check.mjs index c296157..60403dc 100644 --- a/scripts/pack-check.mjs +++ b/scripts/pack-check.mjs @@ -19,7 +19,7 @@ import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const scratch = await mkdtemp(join(tmpdir(), "misofm-codec-packed-")); -const peers = ["4.0.0-rc.112", "4.0.0-rc.115"]; +const peers = ["4.0.3", "4.0.4"]; const receipts = []; const packSource = process.env.CODEC_PACK_SOURCE; const expectedSha256 = process.env.CODEC_PACK_EXPECTED_SHA256; @@ -117,6 +117,7 @@ try { "wasm/flac-encoder.wasm", "wasm/flac-decoder.wasm", "THIRD_PARTY_NOTICES.md", + "CHANGELOG.md", "docs/architecture.md", "docs/adapter-integration.md", "vendor/licenses/libFLAC.txt",