Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/ISSUE_SPECS/010-move-the-codec-to-stable-effect-4-0-x.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion .github/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,5 @@ The existing consumer script accepts these optional environment variables:

A registry check uses `CODEC_PACK_SOURCE=@misofm/codec@<version>` 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.
73 changes: 73 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -35,6 +35,7 @@
"wasm/*.wasm",
"wasm/*.manifest.json",
"README.md",
"CHANGELOG.md",
"docs/*.md",
"LICENSE",
"THIRD_PARTY_NOTICES.md",
Expand All @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion scripts/pack-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down
Loading