From 060b0bf60b6ce82e418b58ff9e19df430f653bde Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 13 Sep 2026 16:50:30 +0000 Subject: [PATCH 1/6] docs: scope managed engine analysis forwarding --- ...e-and-spectrum-through-browser-sessions.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md diff --git a/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md b/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md new file mode 100644 index 0000000..3009621 --- /dev/null +++ b/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md @@ -0,0 +1,170 @@ +# Forward managed engine response and spectrum through browser sessions + +## Requirements and smallest closable outcome + +Unblock misofm/app #210 with a published adapter that exposes the accepted +engine's focused analysis APIs through its existing session. This is a thin +forwarding and release slice, not a new analysis implementation. + +1. Both `openEngineWebSession` and production `openSparseEngineWebSession` + accept the SDK's approved spectrum preparation options. Preserve existing + no-analysis opens. Pass the accepted #793 multiple-target collection and + its explicit aggregate capture budget to the same `createEngine` call that + owns playback. Each entry retains its target kind, stable ID and exact + channel mask. Forward the existing optional response/spectrum subscription + limits so the app can bound delivery and retained results. Use SDK types and + validation; do not introduce adapter capacities or duplicate ABI layouts. +2. Add these methods directly to `EngineWebSession`, typed from the matching + `BrowserEngine` members: `subscribeTrackResponse`, `queryTrackResponse`, + `subscribeSpectrum`. Forward to the existing engine with its receiver and + return its results/handles unchanged. Consumers call the returned spectrum + handle's `update(request)` for the accepted atomic target/channel/config + update, and `readLatest()`/`close()` normally. Preserve all identities, + L/R vectors, units, availability, epochs, spans and loss accounting. +3. Selection changes must use that SDK transaction without reopening the + session, rebuilding preparation, changing the pump or suspending/resuming + audio. Invalid selection leaves the existing stream intact according to + #793. Paused subscriptions/updates do not start audio. Analysis operations + remain outside the transport/seek queue and do not become terminal playback + failures merely because a query or selection is refused. +4. New entry calls reject while the session is closing/closed, and a + `console: false` session reports the existing `console.not_attached` + refusal. SDK `engine.close()` remains responsible for its subscriptions, + response/spectrum Workers and pending operations; reuse the current adapter + cleanup ordering and cancellation. No second subscription registry, timer, + result cache, Worker owner or callback multiplexer. After physical close + completes, no live handle/callback/Worker from that engine may survive. +5. Preserve the current adapter snapshot-at-open behavior for added mutable + preparation/limit inputs before deferred opening work. Reuse SDK copying + helpers if public; otherwise copy only the small declared option structure. + Do not broaden the API to all `CreateEngineOptions` or expose raw internals. +6. Preserve existing console edits, meter/GR projection, telemetry, source + observation, source diagnostics, loading/progress and seek/close semantics. + Keep canonical `blake3:` identities and cache namespaces, codec 0.1.1 and + current decoder pooling. Warm verification remains sequential 512 KiB reads + bounded by each sparse interval; the separate ingest-span limit remains + 128 KiB. Preserve hashing of implicit zero gaps and split PCM frames, exact + byte-count/digest checks, short-read rejection, deadlines/cancellation, and + success-only readiness/verification timing. No source-store, codec, pump, + Effect-pipeline or UI changes belong here. + + Also preserve adapter 0.5.2's verifier-scoped warm task yields from #93: + unchanged 64 KiB zero-gap checkpoints resume through one lazily allocated, + reused MessageChannel with at most one pending continuation. All-active + verification allocates no channel; cold yielding and the absent-capability + fallback remain unchanged. Cancellation settles without awaiting message + delivery, stale callbacks are harmless, and ports/listeners are finalized + before readiness and identity-lock release. Retain its existing tests; do + not rerun the accepted performance experiment or move this resource into + the new analysis/session ownership. + +## Dependency and public contract binding + +Read-only refresh against merged PR #94 confirms adapter main +`e8b119cc013b205be18f5273bd66ece944b787ad`, version 0.5.2 with SDK 0.2.4. +This supersedes the earlier 0.5.1 / `cced684b` inspection baseline. The only +runtime change is #93's bounded warm zero-gap task continuation; session and +managed-analysis forwarding seams are unchanged. Main's version declaration +alone is not a claim that registry publication completed. Read its `AGENTS.md`. +App #210 is OPEN and explicitly depends on this publication. Fetch again at +implementation start and before landing; preserve primary/unrelated work. +Root has created spec-only worktree `/tmp/miso-adapter-engine-analysis-integration` +on `codex/engine-analysis-forwarding` from this main and matching adapter #95. + +Engine #793 is still in progress in `/tmp/miso-engine-spectrum-switch`; +inspection at HEAD `ab2484ca4e802b78a8cf25025451dcff73ae3844` is not acceptance. +Current shared source declares `SpectrumCollection` with `entries` of +`{ target, channels? }` and aggregate `maximumCaptureBytes`. Current managed +handles already expose `update`, but browser `CreateEngineOptions` still only +declares singular `spectrum`; browser collection forwarding is unfinished. +Therefore implementation waits for #793 PASS and its published SDK. Record +the accepted browser option name/type and exact registry SDK version/source/ +archive integrity before coding. Do not infer a browser field from the +headless implementation or forward unfinished host messages manually. + +The three session method names above are existing public SDK names. Preparation +and subscription-limit option names/types must follow the accepted +`CreateEngineOptions` exactly; singular and collection preparation retain the +SDK's exclusivity rules. App #210 uses both lanes of `trackPostMatrix` for all +selectable tracks and one active panel, but the adapter must not hardcode that +app policy or narrow the SDK's supported targets. No one-shot spectrum, +resident-observation migration or additional discovery forwarding is required. +Paused requested-configuration previews use the SDK's existing public preview +API directly in the app; no adapter preview service is needed. + +## Implementation locations and checkpoints + +1. **Forwarding checkpoint.** In `src/session-types.ts`, extend the common + options shared by dense/sparse opens and `EngineWebSession`; use SDK indexed + member/option types where practical. In `src/session.ts::openSessionCommon`, + snapshot/pass the approved options through the existing `engineOptions` + construction and add the three guarded delegates to the returned session. + Keep the current scratch Worker/prepared module reuse; adapt that boundary + only if the accepted SDK's public preparation contract requires it. Existing + `src/index.ts` already exports both common options and the session type, so + no duplicate SDK type facade is necessary. `src/console.ts` needs no change. + Add focused coverage in `tests/session.test.ts` and one README example. +2. **Package/release checkpoint.** After SDK publication, update exact SDK and + adapter versions in `package.json`, `package-lock.json`, + `scripts/check-package.mjs`, `src/provenance.ts`, README and release workflow + pins. Derive provenance from the actual registry SDK archive; retain existing + source-adaptation provenance and codec/legal assets. Choose an unused adapter + version at delivery time rather than assuming a future version now. Qualify + the final package and publish with the existing workflow below. + +New wrapper lifecycle/console-opt-out errors remain `EngineWebAdapterError`; +forwarded SDK semantic failures and returned handle methods preserve the SDK's +own typed contract. Document this boundary rather than claiming all delegated +handle errors are adapter errors, wrapping every handle, or changing existing +delivery errors. Document that analysis is optional and source observation +remains available; remove any blanket README implication that all FFT work must +be implemented by app consumers. + +## Smallest discriminating gates + +- Extend existing session tests to prove both open paths preserve preparation + options/limits and all three public delegates, including option mutation + across an opening await, console opt-out and close. Reuse existing hooks and + actual SDK wherever they already exist; do not add a create-engine abstraction + just for testing. Test late completion at the existing close seam only as + needed to prove adapter forwarding does not extend engine lifetime. +- Extend `scripts/browser-packed.mjs` and its generated strict TypeScript + consumer. In one existing packed Chromium run, open a sparse session with + two prepared tracks and actual packaged pump, engine Worklet and response + Worker. Obtain one managed response, one one-shot response and a ready spectrum; + update the spectrum handle to the second stable track and observe its new + metadata/result while the same context/host/pump remain running. One rejected + target must leave the valid selection usable; close must terminate delivery. + Reuse existing local PCM/sparse fixture and asset-request instrumentation; + distinct spectrum tones and the full DSP accuracy corpus are not needed to + test adapter forwarding. Require the SDK response Worker asset to load from + the packed consumer with correct MIME and no failed asset requests. Existing + cold/warm, meter/console and cleanup assertions remain in the run. No new + harness, browser matrix, remote live profile or OPFS qualification campaign. +- Run `npm run check` and the existing `npm run test:browser` on the final + candidate, then `npm run publish:dry-run`. The packed browser runner copies + exact locked registry dependencies into its isolated tarball consumer; record + that fact honestly. Also install the tarball using ordinary npm in a fresh + directory for strict public-type/import verification, so copying dependencies + does not substitute for dependency-resolution proof. Use the runner's existing + consumer-check source, not a second test framework. No tarballs/caches/dist + output committed. + +## Publication and completion + +Use `.github/workflows/npm-publish.yml`, not a new release mechanism. It is +manual `workflow_dispatch`, accepts `mode: publish|verify` and exact 40-character +`expected_sha`, runs on main, pins Node 22.23.2/npm 11.19.0 and Emscripten 6.0.9, +and publishes through OIDC only. Update its package-version display/env and +both exact SDK dependency guards. Preserve immutable-version refusal, registry +verification, fresh registry import checks and `npm audit signatures +--include-attestations`. Never retry publishing an existing immutable version; +use verify mode after an ambiguous publication outcome. + +Root synchronizes numbered local/GitHub issue #95 before implementation, +records coherent checkpoints and the requested implementer/reviewer workflow, +and merges only after independent PASS and required CI on current main. Dispatch +publication for the actual merged SHA. Completion requires the registry version +and exact SDK dependency, source/provenance, package integrity and signature/ +attestation evidence, then synchronized issue closure. Report that package pair +to app #210; a source-only PASS or local tarball does not unblock app delivery. From 6fd424a66a214cba1c61125869050525e528cdac Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 13 Sep 2026 23:24:45 +0000 Subject: [PATCH 2/6] docs: replace analysis wrappers with shared SDK ownership scope --- ...rowser-session-measurements-and-control.md | 136 ++++++++++++++ ...e-and-spectrum-through-browser-sessions.md | 170 ------------------ 2 files changed, 136 insertions(+), 170 deletions(-) create mode 100644 .github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md delete mode 100644 .github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md diff --git a/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md new file mode 100644 index 0000000..707eff9 --- /dev/null +++ b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md @@ -0,0 +1,136 @@ +# Expose the same SDK engine and delegate browser-session measurements and control + +Supersedes the former three-analysis-delegate plan in this issue. Depends on +misofm/engine#796 for the frozen SDK API and misofm/engine#794 for final published +SDK provenance. Downstream: misofm/app#210. PCM readiness and backend capability +successors are separate and are not prerequisites for this issue. + +## Outcome and baseline + +An adapter session supplies FLAC/sparse storage and PCM while its consumer uses +one SDK engine directly for controls, observations, meters, response and spectrum. +Expose the existing playback engine; a getter alone is insufficient if adapter +`HostFeed`, canonical measurement types or a second console remain. + +Current main: `f833303f146de7cbe1705fe88ae68a6d6e0d4e45`, adapter 0.5.4, +SDK 0.2.4 and codec 0.1.1. Engine accepted main is +`69c268f240bf30b2a43b43dd521120cd89dcc0b9`; misofm/engine#793 is CLOSED and its SDK 0.2.5 +metadata is merged but the registry still reports 0.2.4 on 2026-09-13. +These are inspection facts; recheck before implementation and release. + +## Frozen implementation contract + +1. Export `SessionEngine = Omit, "close">` + and add `readonly engine: SessionEngine` to `EngineWebSession`. Return the + identical existing engine object, retaining receivers. This is a borrowed + TypeScript view, not runtime isolation. The composition session owns close: + consumers call `session.close()` and must not close its host/context/engine + separately or start work after requesting aggregate close. Accessing the + getter during closing/closed rejects with existing `session.closed`. +2. Obtain `await engine.console()` in `attachSessionControl(engine)`, preserving + its managed-observation hook. An existing narrow `session.console` guard may + delegate `edit`/`submit`; it must not call `createBrowserConsole(host)` or + construct another console. Preserve exact SDK command reports and typed + semantic failures. Ordinary aliases refuse once aggregate closing begins. +3. Delete adapter `HostFeed`. SDK `subscribeMeters`/`subscribeTelemetry` own + leases, listener multiplexing, errors and engine disposal. Keep `session.meters` + and `session.telemetry` as deprecated compatibility paths to those methods. + Telemetry is an SDK-type alias and returns the SDK update unchanged. +4. The sole temporary meter compatibility projection may add legacy + `peak = Math.max(peakLeft, peakRight)` to track/master records. Define its + types as SDK types extended with that field, never a second canonical model. + Spread all canonical update/record fields so generation, validity, loss, + spans and future fields survive; preserve master `gainReductionDb: null`. + Correct old null-to-zero types/tests/docs deliberately. This stateless + per-callback projection adds no subscription owner, timer, cache or Worker. + New consumers use `session.engine.subscribeMeters` and app-owned display math. +5. Both dense and sparse opens accept SDK-typed `spectrum`, + `spectrumCollection`, `observationSubscriptionLimits`, + `responseSubscriptionLimits` and `spectrumSubscriptionLimits` and forward + them to that one `createEngine`. Keep singular/collection exclusivity and + exact prepared target/mask/budget semantics. Snapshot mutable options before + the first opening await, using public SDK copying helpers where available + or only the small declared structure. Preserve scratch-compiled module reuse; the accepted SDK owns preparation + validation and its boot contract. + No adapter-specific target policy or broad `CreateEngineOptions` spread. +6. Do not add `session.subscribeTrackResponse`, `session.queryTrackResponse` + or `session.subscribeSpectrum` mirrors. Use the same SDK engine for all + those operations; their failures are optional analysis failures, outside the + source seek/transport queue. Document native SDK errors on direct engine + calls and legacy adapter errors only at existing convenience boundaries. + `console: false` retains adapter console/meter/telemetry refusals; the exposed + engine retains its own SDK policy semantics, without a new adapter facade. +7. Keep aggregate cleanup order, retries and late acquisition handling. + SDK `engine.close()` closes SDK-owned leases/subscriptions/Workers; adapter + closes producer, feed, output and source/store lifetime through its existing + cleanup. No second engine, context, Worker pool or disposer registry. + +## Preserve launch source behavior + +Do not alter FLAC/sparse resolution, BLAKE3 identities/cache namespace, canonical +count/digest verification, integer PCM conversion, OPFS, ring/feed/scratch +protocol, seek/prefill algorithm, prefetch or Effect control pipelines. Preserve +the current preparation-owned warm pool: at most three workers, further bounded +by concurrency/funded 2 MiB slots; one current 512 KiB payload per worker, +interval-local reads, 128 KiB ingest bound, 64 KiB zero checkpoints, consumed +buffer disposal, warm MessageChannel yields and main-realm fallback. Keep cold +capacity, Blob/index identity, cancellation/physical cleanup before locks and +readiness, diagnostics and packaged verification assets. No new source format, +IndexedDB backend, selector, storage framework or performance experiment. + +## Locations and checkpoints + +`src/session-types.ts`, `src/session.ts`, `src/console.ts`, public exports, +`tests/console.test.ts`, `tests/session.test.ts`, README, existing packed consumer. +Refresh/integrate latest main in the isolated worktree; do not touch unrelated +primary changes. Source implementation can use an exact accepted SDK tarball +before registry publication, with temporary installation state outside commits. +Final dependency pins/package acceptance require the registry SDK from misofm/engine#794. + +1. Same engine/control/measurement ownership and option forwarding; focused + types/tests green, then root commits exact paths. +2. Upgrade the exact SDK dependency and adapter release identities using existing + scripts/workflow; packed qualification, fresh review and merged delivery. + +## Focused gates and publication + +- Both open paths expose object-identical `engine`/host/context; repeated console + acquisition uses the SDK's existing owner hook. A managed observation/manual + console conflict retains SDK behavior. Multiple direct and compatibility + measurement subscriptions share one host lease; all canonical fields and null + survive legacy projection. `console: false`, late acquisition and close retain + existing contracts; no callbacks survive completed aggregate close. +- Mutate input preparation/limits across an opening await and prove snapshotted + values reach the existing engine. Preserve ordinary no-analysis opening. +- Extend `scripts/browser-packed.mjs` and its strict consumer, using one existing + packed Chromium scenario: actual sparse session/pump/Worklet/response Worker, + real SDK meters, one managed response and spectrum, atomic target switch + while context/pump remain running, refused target preserving the old stream, + close and no asset failures. Reuse existing fixture and cold/warm assertions. + This tests delegation; the app owns full numerical plotting acceptance. +- Run `npm run check`, `npm run test:browser` and `npm run publish:dry-run` on + the final candidate. The browser runner copies locked dependencies; also + install the tarball normally in a fresh directory and compile/import its + public surface to prove registry dependency resolution. No new harness/matrix. +- Choose an unused adapter version at release time. Update existing package/ + lock, `src/provenance.ts`, `scripts/check-package.mjs`, README and workflow + exact-version/SDK guards from actual registry bytes. Preserve legal/codec + assets and trusted release checks. Use existing `npm-publish.yml` + `publish|verify` with exact merged main SHA, OIDC and existing attestation + checks; an ambiguous publish is recovered with verify, never republishing. + +## Workflow and completion + +Luna XHIGH implements; a fresh Astra MEDIUM verifies and fixes concrete bugs +within this brief only; the fresh Astra MEDIUM coordinator is distinct. Maximum +five coherent attempts, one verdict each; stop/rebrief after the fifth failure. +Checkpoint focused-green tranches, run required CI, preserve exact source/package +evidence and synchronize local/GitHub state. Completion requires independent +PASS, merged source, published package and exact registry SDK dependency, +archive integrity and provenance/attestation proof, then verified issue closure. +A source PASS or tarball alone does not establish published app dependencies. + +## Decision record + +Scope amendment only. The old three-method delegation and instruction to retain +adapter measurement ownership are superseded. No new runtime result is claimed. diff --git a/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md b/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md deleted file mode 100644 index 3009621..0000000 --- a/.github/ISSUE_SPECS/95-forward-managed-engine-response-and-spectrum-through-browser-sessions.md +++ /dev/null @@ -1,170 +0,0 @@ -# Forward managed engine response and spectrum through browser sessions - -## Requirements and smallest closable outcome - -Unblock misofm/app #210 with a published adapter that exposes the accepted -engine's focused analysis APIs through its existing session. This is a thin -forwarding and release slice, not a new analysis implementation. - -1. Both `openEngineWebSession` and production `openSparseEngineWebSession` - accept the SDK's approved spectrum preparation options. Preserve existing - no-analysis opens. Pass the accepted #793 multiple-target collection and - its explicit aggregate capture budget to the same `createEngine` call that - owns playback. Each entry retains its target kind, stable ID and exact - channel mask. Forward the existing optional response/spectrum subscription - limits so the app can bound delivery and retained results. Use SDK types and - validation; do not introduce adapter capacities or duplicate ABI layouts. -2. Add these methods directly to `EngineWebSession`, typed from the matching - `BrowserEngine` members: `subscribeTrackResponse`, `queryTrackResponse`, - `subscribeSpectrum`. Forward to the existing engine with its receiver and - return its results/handles unchanged. Consumers call the returned spectrum - handle's `update(request)` for the accepted atomic target/channel/config - update, and `readLatest()`/`close()` normally. Preserve all identities, - L/R vectors, units, availability, epochs, spans and loss accounting. -3. Selection changes must use that SDK transaction without reopening the - session, rebuilding preparation, changing the pump or suspending/resuming - audio. Invalid selection leaves the existing stream intact according to - #793. Paused subscriptions/updates do not start audio. Analysis operations - remain outside the transport/seek queue and do not become terminal playback - failures merely because a query or selection is refused. -4. New entry calls reject while the session is closing/closed, and a - `console: false` session reports the existing `console.not_attached` - refusal. SDK `engine.close()` remains responsible for its subscriptions, - response/spectrum Workers and pending operations; reuse the current adapter - cleanup ordering and cancellation. No second subscription registry, timer, - result cache, Worker owner or callback multiplexer. After physical close - completes, no live handle/callback/Worker from that engine may survive. -5. Preserve the current adapter snapshot-at-open behavior for added mutable - preparation/limit inputs before deferred opening work. Reuse SDK copying - helpers if public; otherwise copy only the small declared option structure. - Do not broaden the API to all `CreateEngineOptions` or expose raw internals. -6. Preserve existing console edits, meter/GR projection, telemetry, source - observation, source diagnostics, loading/progress and seek/close semantics. - Keep canonical `blake3:` identities and cache namespaces, codec 0.1.1 and - current decoder pooling. Warm verification remains sequential 512 KiB reads - bounded by each sparse interval; the separate ingest-span limit remains - 128 KiB. Preserve hashing of implicit zero gaps and split PCM frames, exact - byte-count/digest checks, short-read rejection, deadlines/cancellation, and - success-only readiness/verification timing. No source-store, codec, pump, - Effect-pipeline or UI changes belong here. - - Also preserve adapter 0.5.2's verifier-scoped warm task yields from #93: - unchanged 64 KiB zero-gap checkpoints resume through one lazily allocated, - reused MessageChannel with at most one pending continuation. All-active - verification allocates no channel; cold yielding and the absent-capability - fallback remain unchanged. Cancellation settles without awaiting message - delivery, stale callbacks are harmless, and ports/listeners are finalized - before readiness and identity-lock release. Retain its existing tests; do - not rerun the accepted performance experiment or move this resource into - the new analysis/session ownership. - -## Dependency and public contract binding - -Read-only refresh against merged PR #94 confirms adapter main -`e8b119cc013b205be18f5273bd66ece944b787ad`, version 0.5.2 with SDK 0.2.4. -This supersedes the earlier 0.5.1 / `cced684b` inspection baseline. The only -runtime change is #93's bounded warm zero-gap task continuation; session and -managed-analysis forwarding seams are unchanged. Main's version declaration -alone is not a claim that registry publication completed. Read its `AGENTS.md`. -App #210 is OPEN and explicitly depends on this publication. Fetch again at -implementation start and before landing; preserve primary/unrelated work. -Root has created spec-only worktree `/tmp/miso-adapter-engine-analysis-integration` -on `codex/engine-analysis-forwarding` from this main and matching adapter #95. - -Engine #793 is still in progress in `/tmp/miso-engine-spectrum-switch`; -inspection at HEAD `ab2484ca4e802b78a8cf25025451dcff73ae3844` is not acceptance. -Current shared source declares `SpectrumCollection` with `entries` of -`{ target, channels? }` and aggregate `maximumCaptureBytes`. Current managed -handles already expose `update`, but browser `CreateEngineOptions` still only -declares singular `spectrum`; browser collection forwarding is unfinished. -Therefore implementation waits for #793 PASS and its published SDK. Record -the accepted browser option name/type and exact registry SDK version/source/ -archive integrity before coding. Do not infer a browser field from the -headless implementation or forward unfinished host messages manually. - -The three session method names above are existing public SDK names. Preparation -and subscription-limit option names/types must follow the accepted -`CreateEngineOptions` exactly; singular and collection preparation retain the -SDK's exclusivity rules. App #210 uses both lanes of `trackPostMatrix` for all -selectable tracks and one active panel, but the adapter must not hardcode that -app policy or narrow the SDK's supported targets. No one-shot spectrum, -resident-observation migration or additional discovery forwarding is required. -Paused requested-configuration previews use the SDK's existing public preview -API directly in the app; no adapter preview service is needed. - -## Implementation locations and checkpoints - -1. **Forwarding checkpoint.** In `src/session-types.ts`, extend the common - options shared by dense/sparse opens and `EngineWebSession`; use SDK indexed - member/option types where practical. In `src/session.ts::openSessionCommon`, - snapshot/pass the approved options through the existing `engineOptions` - construction and add the three guarded delegates to the returned session. - Keep the current scratch Worker/prepared module reuse; adapt that boundary - only if the accepted SDK's public preparation contract requires it. Existing - `src/index.ts` already exports both common options and the session type, so - no duplicate SDK type facade is necessary. `src/console.ts` needs no change. - Add focused coverage in `tests/session.test.ts` and one README example. -2. **Package/release checkpoint.** After SDK publication, update exact SDK and - adapter versions in `package.json`, `package-lock.json`, - `scripts/check-package.mjs`, `src/provenance.ts`, README and release workflow - pins. Derive provenance from the actual registry SDK archive; retain existing - source-adaptation provenance and codec/legal assets. Choose an unused adapter - version at delivery time rather than assuming a future version now. Qualify - the final package and publish with the existing workflow below. - -New wrapper lifecycle/console-opt-out errors remain `EngineWebAdapterError`; -forwarded SDK semantic failures and returned handle methods preserve the SDK's -own typed contract. Document this boundary rather than claiming all delegated -handle errors are adapter errors, wrapping every handle, or changing existing -delivery errors. Document that analysis is optional and source observation -remains available; remove any blanket README implication that all FFT work must -be implemented by app consumers. - -## Smallest discriminating gates - -- Extend existing session tests to prove both open paths preserve preparation - options/limits and all three public delegates, including option mutation - across an opening await, console opt-out and close. Reuse existing hooks and - actual SDK wherever they already exist; do not add a create-engine abstraction - just for testing. Test late completion at the existing close seam only as - needed to prove adapter forwarding does not extend engine lifetime. -- Extend `scripts/browser-packed.mjs` and its generated strict TypeScript - consumer. In one existing packed Chromium run, open a sparse session with - two prepared tracks and actual packaged pump, engine Worklet and response - Worker. Obtain one managed response, one one-shot response and a ready spectrum; - update the spectrum handle to the second stable track and observe its new - metadata/result while the same context/host/pump remain running. One rejected - target must leave the valid selection usable; close must terminate delivery. - Reuse existing local PCM/sparse fixture and asset-request instrumentation; - distinct spectrum tones and the full DSP accuracy corpus are not needed to - test adapter forwarding. Require the SDK response Worker asset to load from - the packed consumer with correct MIME and no failed asset requests. Existing - cold/warm, meter/console and cleanup assertions remain in the run. No new - harness, browser matrix, remote live profile or OPFS qualification campaign. -- Run `npm run check` and the existing `npm run test:browser` on the final - candidate, then `npm run publish:dry-run`. The packed browser runner copies - exact locked registry dependencies into its isolated tarball consumer; record - that fact honestly. Also install the tarball using ordinary npm in a fresh - directory for strict public-type/import verification, so copying dependencies - does not substitute for dependency-resolution proof. Use the runner's existing - consumer-check source, not a second test framework. No tarballs/caches/dist - output committed. - -## Publication and completion - -Use `.github/workflows/npm-publish.yml`, not a new release mechanism. It is -manual `workflow_dispatch`, accepts `mode: publish|verify` and exact 40-character -`expected_sha`, runs on main, pins Node 22.23.2/npm 11.19.0 and Emscripten 6.0.9, -and publishes through OIDC only. Update its package-version display/env and -both exact SDK dependency guards. Preserve immutable-version refusal, registry -verification, fresh registry import checks and `npm audit signatures ---include-attestations`. Never retry publishing an existing immutable version; -use verify mode after an ambiguous publication outcome. - -Root synchronizes numbered local/GitHub issue #95 before implementation, -records coherent checkpoints and the requested implementer/reviewer workflow, -and merges only after independent PASS and required CI on current main. Dispatch -publication for the actual merged SHA. Completion requires the registry version -and exact SDK dependency, source/provenance, package integrity and signature/ -attestation evidence, then synchronized issue closure. Report that package pair -to app #210; a source-only PASS or local tarball does not unblock app delivery. From 3d8325e287495176fadecc39cd22d99453e4eee3 Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 13 Sep 2026 23:27:08 +0000 Subject: [PATCH 3/6] docs: freeze cross-repo SDK boundary implementation issues --- ...-prefill-proof-to-the-sdk-runway-helper.md | 65 +++++++++++++++++ ...ments-only-to-the-selected-storage-path.md | 71 +++++++++++++++++++ ...rowser-session-measurements-and-control.md | 2 + 3 files changed, 138 insertions(+) create mode 100644 .github/ISSUE_SPECS/101-delegate-session-pcm-prefill-proof-to-the-sdk-runway-helper.md create mode 100644 .github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md diff --git a/.github/ISSUE_SPECS/101-delegate-session-pcm-prefill-proof-to-the-sdk-runway-helper.md b/.github/ISSUE_SPECS/101-delegate-session-pcm-prefill-proof-to-the-sdk-runway-helper.md new file mode 100644 index 0000000..d931ed5 --- /dev/null +++ b/.github/ISSUE_SPECS/101-delegate-session-pcm-prefill-proof-to-the-sdk-runway-helper.md @@ -0,0 +1,65 @@ +# Delegate session PCM prefill proof to the SDK runway helper + +Depends on misofm/engine#797 and a published SDK containing its +`waitForPcmRunway` contract for final package acceptance. Independent of adapter +misofm/engine-web-adapter#95, misofm/engine-web-adapter#102 and app misofm/app#210; never hold their delivery for this work. + +## Smallest outcome and facts + +Dense and sparse session opening/seek use the SDK's generic PCM runway proof, +leaving this adapter responsible for its producer and aggregate choreography. +Current source baseline is main +`f833303f146de7cbe1705fe88ae68a6d6e0d4e45`; refresh before implementation. +`src/feed.ts`, `src/scratch.ts` and `src/stems/ring.ts` already delegate to SDK +primitives. Only `src/session.ts::waitForRunway` duplicates the generic generation, +contiguity and ring-capacity proof. This is source-level ownership evidence. + +## Required change + +1. Replace that algorithm with SDK `waitForPcmRunway`. Opening supplies existing + source totals/rings, frame 0, generation 1 and the current two-second timeout; + seek supplies its acknowledged target/full generation and the same deadline. + Omit `minimumFrames` to retain today's full-capacity/EOF-clamped policy. +2. Keep source `pump.seekFrames`, feed attachment/`prepareSeek`, context-state + checks, suspension/resumption, serialized transport, producer/refill work and + terminal cleanup exactly at their current composition seam. The helper never + grants permission to skip these steps or resumes audio itself. +3. Remove now-unused per-prefill observer construction and direct protocol + checks. Keep the existing diagnostic/source-observation objects because they + have independent callers. Do not alter ring/read cursor behavior or prefetch. +4. Translate `PcmRunwayError` into existing `session.open`/`session.seek` failures, + preserving source ID, cause and timeout/mismatch distinction in details. + Preserve abort/close semantics and meaningful SDK usage errors. No second + fallback algorithm or copy retained after migration. +5. Preserve dense/sparse canonical BLAKE3/count checks, warm/cold worker limits, + 512 KiB reads, 128 KiB ingest bounds, 64 KiB zero checkpoints, source-specific + cleanup/locks, current diagnostics and all playback behavior. No codec, + storage framework, source-policy expansion or transport rewrite. + +## Focused proof and delivery + +Files: `src/session.ts`, an existing error translation seam only if needed, +`tests/session.test.ts`, README and package/provenance pins when adopting the +published SDK. Reuse existing opening, suspended/running seek and failure tests: +full current-generation prefill before success, wrong generation/contiguity, +EOF, timeout/cancellation, close during seek, and restore-running only after +successful proof. Assert consumer preparation precedes the SDK call and a +failure still completes aggregate cleanup. SDK owns detailed ring unit cases; +do not duplicate its matrix or add an engine factory just for testing. + +Root commits one focused-green implementation tranche. Run `npm run check`; +for a released candidate run existing packed-browser/publish-dry-run gates and +fresh-consumer dependency checks once. Package with another coherent adapter +release when available; do not delay misofm/engine-web-adapter#95/app plotting to manufacture batching. +Final published adoption needs exact SDK/adapter versions, merged source SHAs, +registry integrities and existing provenance/attestation checks. If it cannot +share misofm/engine-web-adapter#95's cut, use a stateless ordinary release issue for publication and +record that dependency explicitly; source availability is never registry proof. + +Luna XHIGH implements; a fresh Astra MEDIUM independently verifies and fixes +concrete in-scope bugs only; coordinator is a separate fresh Astra MEDIUM. +Maximum five coherent attempts with one verdict each; preserve evidence and +rebrief after five failures. Root synchronizes numbered local/GitHub evidence, +integrates latest main, and closes after PASS, merged delivery, required CI and +the published adoption evidence; verify remote CLOSED. No new results claimed +by this scope-only brief. diff --git a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md new file mode 100644 index 0000000..e99cf4a --- /dev/null +++ b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md @@ -0,0 +1,71 @@ +# Apply OPFS capability requirements only to the selected storage path + +Independent of adapter misofm/engine-web-adapter#95/#101 and engine/app analysis work. +This issue does not implement IndexedDB, WAVPACK or another backend. + +## Outcome and source evidence + +A caller supplying an existing custom dense or sparse store can open a session +without unrelated OPFS/Web Locks browser APIs. Default OPFS sessions retain +their current early, typed capability refusals and Worker-side write probe. + +At adapter main `f833303f146de7cbe1705fe88ae68a6d6e0d4e45`, +`src/session.ts::openSessionCommon` unconditionally calls +`assertEngineWebCapabilities` before selecting a store. That checker in +`src/capabilities.ts` requires `navigator.storage.getDirectory`, +`FileSystemFileHandle` and Web Locks even when `options.store` supplies the +whole storage service. `OpfsStorageBackend.open()` in `src/stems/storage.ts` +already probes actual Worker-only write support before source resolution. +This establishes an unnecessary source-level gate, not a demonstrated production +incident. Refresh latest main and read its AGENTS before implementing. + +## Frozen correction + +1. Separate the existing runtime predicates (cross-origin isolation, SAB, + Worker, AudioContext/AudioWorklet and Wasm SIMD128) from OPFS persistence + predicates in `src/capabilities.ts`. Reuse existing errors/remedies. +2. Preserve the existing module-level `assertEngineWebCapabilities(scope?)` as the comprehensive + default-FLAC/OPFS preflight for compatibility. Use two small internal/exported + module helpers, `assertEngineRuntimeCapabilities(scope?)` and + `assertOpfsStorageCapabilities(scope?)`, to compose its existing behavior. + Export from the package only if a current public use requires it; no new + capability registry, backend descriptor or plug-in protocol. +3. Both open entry points snapshot whether a store was supplied before awaiting + work. `openSessionCommon` always runs runtime checks; run OPFS checks only + for the default store path, before creating it or invoking resolver/network + work. Sparse opening currently removes `store` from `commonOptions`, so pass + the selected-path fact explicitly; do not accidentally treat it as default. +4. A supplied store owns its persistence/locking requirements. Do not inspect + its class name, assume all custom stores use OPFS, or disable actual backend + errors. Injected OPFS still executes its own `open()`/Worker support checks. + Default OPFS retains window-visible file handles/getDirectory and Web Locks + checks, Worker write handshake and existing typed failures/remedies. +5. Preserve source validation/admission ordering, BLAKE3/count verification, + current locking and cleanup contracts, warm/cold paths and output behavior. + There is no promise that a custom store is safe on multiple tabs without its + own coordination. Do not modify locks, storage algorithms or source policy. + +## Small proof and completion + +Change `src/capabilities.ts`, the common-open selected-path argument and focused +`tests/session.test.ts` cases plus concise API docs. Existing memory/custom-store +fixtures must reach normal opening for dense and sparse paths with valid runtime +capabilities but no OPFS/file handles/Web Locks. Missing runtime requirements +must still refuse before store/resolver work. Default-store cases must still +refuse missing persistence APIs before source work and retain the existing +Worker write-support failure/cleanup case. A supplied store's own failure must +propagate. Reuse the existing default OPFS packed smoke; no new browser/backend +matrix or IndexedDB test framework. + +Run focused tests and `npm run check`, then current required CI. One coherent +checkpoint should suffice; root commits focused-green exact paths before more +implementation. Luna XHIGH implements, a fresh Astra MEDIUM verifies and may +fix concrete in-scope bugs only, and a separate fresh Astra MEDIUM coordinates. +At most five coherent attempts/one verdict each; after five failures stop and +rebrief. Root records the source/evidence and closes local/GitHub state after +independent PASS, merged delivery and required CI; verify remote CLOSED. +If package publication is deferred, explicitly record the ordinary release +successor and do not claim registry availability. It may share a ready adapter +release cut, but misofm/engine-web-adapter#95 and app misofm/app#210 do not wait for this independent correction. + +Decision record: scope-only; no implementation or fresh runtime result yet. diff --git a/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md index 707eff9..8fd6274 100644 --- a/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md +++ b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md @@ -134,3 +134,5 @@ A source PASS or tarball alone does not establish published app dependencies. Scope amendment only. The old three-method delegation and instruction to retain adapter measurement ownership are superseded. No new runtime result is claimed. + +Coordination entry point: https://github.com/misofm/engine/issues/796 . Independent follow-ups https://github.com/misofm/engine/issues/797 , https://github.com/misofm/engine-web-adapter/issues/101 and https://github.com/misofm/engine-web-adapter/issues/102 do not block this issue or app #210. From 2a32d46d2544e25bc4d593d8d9eac8b58bec2e9f Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 13 Sep 2026 23:27:39 +0000 Subject: [PATCH 4/6] docs: make cross-repository handoff references explicit --- ...capability-requirements-only-to-the-selected-storage-path.md | 2 +- ...ine-and-delegate-browser-session-measurements-and-control.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md index e99cf4a..422bb34 100644 --- a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md +++ b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md @@ -1,6 +1,6 @@ # Apply OPFS capability requirements only to the selected storage path -Independent of adapter misofm/engine-web-adapter#95/#101 and engine/app analysis work. +Independent of adapter misofm/engine-web-adapter#95/misofm/engine-web-adapter#101 and engine/app analysis work. This issue does not implement IndexedDB, WAVPACK or another backend. ## Outcome and source evidence diff --git a/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md index 8fd6274..07efd55 100644 --- a/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md +++ b/.github/ISSUE_SPECS/95-expose-the-same-sdk-engine-and-delegate-browser-session-measurements-and-control.md @@ -135,4 +135,4 @@ A source PASS or tarball alone does not establish published app dependencies. Scope amendment only. The old three-method delegation and instruction to retain adapter measurement ownership are superseded. No new runtime result is claimed. -Coordination entry point: https://github.com/misofm/engine/issues/796 . Independent follow-ups https://github.com/misofm/engine/issues/797 , https://github.com/misofm/engine-web-adapter/issues/101 and https://github.com/misofm/engine-web-adapter/issues/102 do not block this issue or app #210. +Coordination entry point: https://github.com/misofm/engine/issues/796 . Independent follow-ups https://github.com/misofm/engine/issues/797 , https://github.com/misofm/engine-web-adapter/issues/101 and https://github.com/misofm/engine-web-adapter/issues/102 do not block this issue or app misofm/app#210. From 6616d01b7560d35975ad0b9733f97e7f76cfe708 Mon Sep 17 00:00:00 2001 From: BL Date: Mon, 14 Sep 2026 00:19:19 +0000 Subject: [PATCH 5/6] fix: require OPFS capabilities only for the selected store path --- ...ments-only-to-the-selected-storage-path.md | 14 ++++ README.md | 6 ++ src/capabilities.ts | 16 ++++- src/session.ts | 13 ++-- tests/session.test.ts | 66 ++++++++++++++++++- 5 files changed, 106 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md index 422bb34..f449da3 100644 --- a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md +++ b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md @@ -69,3 +69,17 @@ successor and do not claim registry availability. It may share a ready adapter release cut, but misofm/engine-web-adapter#95 and app misofm/app#210 do not wait for this independent correction. Decision record: scope-only; no implementation or fresh runtime result yet. + + +## Implementation checkpoint + +Luna XHIGH implemented the frozen selected-path correction in isolated +`codex/backend-capabilities`: runtime and OPFS preflights compose the existing +comprehensive checker; dense and sparse opens snapshot whether a store was +supplied and skip persistence predicates only for that selected custom path. +Default-store refusal order and actual backend failures remain intact. +Implementer reports focused session 45/45, full `npm run check` 367/367 with +package policy, and whitespace checks PASS. Root checkpoints the four intended +source/test/README paths; no release pins changed. Independent fresh Astra +MEDIUM verification and required merged CI remain pending. This is attempt 1; +no published capability or final acceptance is claimed yet. diff --git a/README.md b/README.md index 02f02cc..5bec005 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,12 @@ the only decoder path. The server must expose exact `Content-Range` and `Content return status 206, avoid `Content-Encoding`, and keep total size and any visible ETag stable across attempts. +Supplying an existing `store` selects caller-owned persistence for either the +dense or sparse session entry point. The adapter still checks the shared Engine +runtime requirements, while the store owns its own persistence and locking +requirements. Omitting `store` selects the default OPFS path and runs the typed +OPFS and Web Locks preflight before scratch boot or source resolution begins. + ### Safari floor The **decoder** is universal: Chromium, macOS Safari, and mobile Safari run the diff --git a/src/capabilities.ts b/src/capabilities.ts index 2152f72..3586671 100644 --- a/src/capabilities.ts +++ b/src/capabilities.ts @@ -25,10 +25,25 @@ export interface WebCapabilityScope { /** Synchronous, allocation-small gates that run before resolver/network work. */ export function assertEngineWebCapabilities(scope: WebCapabilityScope = globalThis): void { + assertEngineRuntimeCapabilities(scope); + assertOpfsStorageCapabilities(scope); +} + +/** + * Check the browser primitives required by every Engine Web session. Storage + * requirements are deliberately kept out of this helper so caller-supplied + * stores can own their persistence capabilities. + */ +export function assertEngineRuntimeCapabilities(scope: WebCapabilityScope = globalThis): void { requireCapability(scope.crossOriginIsolated === true, "capability.cross_origin_isolation", "Cross-origin isolation is required"); requireCapability(typeof scope.SharedArrayBuffer === "function", "capability.shared_array_buffer", "SharedArrayBuffer is required"); requireCapability(typeof scope.Worker === "function", "capability.module_worker", "Module Worker support is required"); requireCapability(typeof scope.AudioContext === "function" && typeof scope.AudioWorkletNode === "function", "capability.audio_worklet", "AudioWorklet is required"); + requireCapability(scope.WebAssembly?.validate(SIMD128_PROBE) === true, "capability.simd128", "WebAssembly SIMD128 is required"); +} + +/** Check the browser primitives used by the default OPFS storage path. */ +export function assertOpfsStorageCapabilities(scope: WebCapabilityScope = globalThis): void { requireCapability( typeof scope.navigator?.storage?.getDirectory === "function", "capability.opfs", @@ -48,7 +63,6 @@ export function assertEngineWebCapabilities(scope: WebCapabilityScope = globalTh { missing: "FileSystemFileHandle", remedy: OPFS_WRITE_REMEDY }, ); requireCapability(typeof scope.navigator?.locks?.request === "function", "capability.web_locks", "Web Locks are required"); - requireCapability(scope.WebAssembly?.validate(SIMD128_PROBE) === true, "capability.simd128", "WebAssembly SIMD128 is required"); } function requireCapability( diff --git a/src/session.ts b/src/session.ts index 3ab2adb..5318f43 100644 --- a/src/session.ts +++ b/src/session.ts @@ -7,7 +7,7 @@ import { createEngine, createDefaultHost, scratchBootOptions, MSB1_CONTROL, Msb1 import type { BrowserEngine, CreateEngineOptions } from "@misofm/engine/browser"; import { ADAPTER_ASSETS } from "./assets.js"; -import { assertEngineWebCapabilities } from "./capabilities.js"; +import { assertEngineRuntimeCapabilities, assertOpfsStorageCapabilities } from "./capabilities.js"; import { attachSessionControl } from "./console.js"; import type { SessionControl } from "./console.js"; import { EngineWebAdapterError } from "./errors.js"; @@ -65,6 +65,7 @@ function sparseSessionCancellationError(cause: unknown): SparseSessionCancellati export async function openEngineWebSession(options: EngineWebSessionOptions): Promise { const { flac, resolver, store, createPump, ingestDiagnostics, assets, onProgress } = options; + const storeSupplied = store !== undefined; bindIngestDiagnostics(ingestDiagnostics); const hasFlac = flac !== undefined; const hasResolver = resolver !== undefined; @@ -75,13 +76,14 @@ export async function openEngineWebSession(options: EngineWebSessionOptions): Pr { hasFlac, hasResolver }, ); } - return openSessionCommon(options, (input) => prepareDenseSources({ + return openSessionCommon(options, storeSupplied, (input) => prepareDenseSources({ ...input, flac, resolver: resolver as StemResolver, store, createPump, ingestDiagnostics, assets, onProgress, })); } export async function openSparseEngineWebSession(options: SparseEngineWebSessionOptions): Promise { const { store, resolver, maximumMetadataBytes, createPump, assets, onProgress, ...commonOptions } = options; + const storeSupplied = store !== undefined; if (onProgress !== undefined && typeof onProgress !== "function") throw new TypeError("Sparse session onProgress must be a function"); const progress = sparseProgressReporter(onProgress); // This entry point has one source contract. Refuse dense/FLAC-shaped input @@ -95,7 +97,7 @@ export async function openSparseEngineWebSession(options: SparseEngineWebSession ); } try { - return await openSessionCommon({ ...commonOptions, ...(assets === undefined ? {} : { assets }), onProgress: progress.emit }, (input) => prepareSparseSources({ + return await openSessionCommon({ ...commonOptions, ...(assets === undefined ? {} : { assets }), onProgress: progress.emit }, storeSupplied, (input) => prepareSparseSources({ ...input, store, resolver, maximumMetadataBytes, createPump, assets, onProgress: progress.emit, })); } finally { @@ -137,8 +139,9 @@ interface SparseSourcePreparationInput extends SourcePreparationInput { readonly onProgress: SparseEngineWebSessionOptions["onProgress"]; } -async function openSessionCommon(options: SessionOpenOptions, prepareSources: PrepareSources): Promise { - assertEngineWebCapabilities(options.capabilityScope); +async function openSessionCommon(options: SessionOpenOptions, storeSupplied: boolean, prepareSources: PrepareSources): Promise { + assertEngineRuntimeCapabilities(options.capabilityScope); + if (!storeSupplied) assertOpfsStorageCapabilities(options.capabilityScope); const abort = new AbortController(); const detachAbort = forwardAbort(options.signal, abort); const cleanup: Array<() => void | Promise> = []; diff --git a/tests/session.test.ts b/tests/session.test.ts index d3fca9a..8142482 100644 --- a/tests/session.test.ts +++ b/tests/session.test.ts @@ -13,7 +13,7 @@ import { createBLAKE3 } from "hash-wasm"; import type { BrowserEngine } from "@misofm/engine/browser"; import { EngineWebAdapterError, openEngineWebSession, openSparseEngineWebSession } from "../src/index.js"; -import { assertEngineWebCapabilities } from "../src/capabilities.js"; +import { assertEngineRuntimeCapabilities, assertEngineWebCapabilities, assertOpfsStorageCapabilities } from "../src/capabilities.js"; import { MSB1_CONTROL } from "../src/stems/ring.js"; import type { EngineAudioContext, EnginePump, EngineWebSessionCommonOptions, EngineWebSessionOptions } from "../src/session-types.js"; import type { FlacWorkerRequest, FlacWorkerResponse } from "../src/stems/flac-worker-protocol.js"; @@ -87,6 +87,60 @@ test("OPFS refusals name what is missing and carry a remedy", () => { } }); +test("runtime and OPFS capability checks compose independently", () => { + const withoutStorage = capabilitiesWithoutOpfs(); + assert.doesNotThrow(() => assertEngineRuntimeCapabilities(withoutStorage)); + assert.throws( + () => assertOpfsStorageCapabilities(withoutStorage), + (error: unknown) => error instanceof EngineWebAdapterError + && error.code === "capability.opfs" + && error.details.missing === "navigator.storage.getDirectory", + ); + + const withoutRuntime = { ...capabilities(), crossOriginIsolated: false }; + assert.throws( + () => assertEngineRuntimeCapabilities(withoutRuntime), + (error: unknown) => error instanceof EngineWebAdapterError && error.code === "capability.cross_origin_isolation", + ); +}); + +test("default dense and sparse stores retain OPFS preflight before source work", async () => { + const base = baseOptions(); + let denseScratch = 0; + let denseResolver = 0; + const denseResolverPath: StemResolver = { + async resolve() { denseResolver += 1; throw new Error("default resolver must not run"); }, + }; + await assert.rejects( + openEngineWebSession({ + ...base, resolver: denseResolverPath, capabilityScope: capabilitiesWithoutOpfs(), + scratchBoot: async () => { denseScratch += 1; throw new Error("scratch must not run"); }, + }), + (error: unknown) => error instanceof EngineWebAdapterError + && error.code === "capability.opfs" + && error.details.missing === "navigator.storage.getDirectory", + ); + assert.equal(denseScratch, 0); + assert.equal(denseResolver, 0); + + let sparseScratch = 0; + let sparseResolver = 0; + await assert.rejects( + openSparseEngineWebSession({ + document: base.document, sources: base.sources!, leaseId: base.leaseId!, + resolver: async () => { sparseResolver += 1; throw new Error("default resolver must not run"); }, + capabilityScope: capabilitiesWithoutOpfs(), + scratchBoot: async () => { sparseScratch += 1; throw new Error("scratch must not run"); }, + // The omitted store selects the default OPFS path. + }), + (error: unknown) => error instanceof EngineWebAdapterError + && error.code === "capability.opfs" + && error.details.missing === "navigator.storage.getDirectory", + ); + assert.equal(sparseScratch, 0); + assert.equal(sparseResolver, 0); +}); + test("default module Worker handshake fails before store or resolver work", async () => { let storeOpened = false; let resolverCalled = false; @@ -234,7 +288,8 @@ test("session composes in order and serializes lifecycle with reverse cleanup", { id: "source-z", spec: { channels: 1, bitDepth: 16, frames: 4, content: IDENTITY_Z } }, { id: "source", spec: { channels: 1, bitDepth: 16, frames: 4, content: IDENTITY } }, ]), - capabilityScope: capabilities(), + // This supplied store owns persistence, so unrelated OPFS APIs are not required. + capabilityScope: capabilitiesWithoutOpfs(), createContext: () => context, createHost: async ({ context: engineContext, preparedModule }) => { assert.equal(preparedModule, scratchWorker!.module, "the locally received module survives verified ingestion"); @@ -351,7 +406,8 @@ test("sparse session prepares complete authoritative sources through the shared } as unknown as BrowserEngine["host"]; const session = await openSparseEngineWebSession({ document: documentFor(sources), sources, leaseId: "sparse-lease", console: false, - capabilityScope: capabilities(), store, + // This supplied store owns persistence, so unrelated OPFS APIs are not required. + capabilityScope: capabilitiesWithoutOpfs(), store, scratchBoot: async () => ({ sampleRateHz: 48_000, quantumFrames: 4, sourceRingFrames: 16, backend: "simd128", tracks: [], sources: compiled.map((source) => ({ id: source.id, channels: source.spec.channels, frames: BigInt(source.spec.frames) })) }), createContext: () => context, @@ -1753,6 +1809,10 @@ function capabilities(): NonNullable }; } +function capabilitiesWithoutOpfs(): NonNullable { + return { ...capabilities(), navigator: {}, FileSystemFileHandle: undefined }; +} + function fakeContext(events: string[], hangResume = false): EngineAudioContext & { modules: string[] } { const modules: string[] = []; let state = "suspended"; From 07af9bb8d1178cb4ca084578bebef1115da79ab4 Mon Sep 17 00:00:00 2001 From: BL Date: Mon, 14 Sep 2026 00:23:58 +0000 Subject: [PATCH 6/6] docs: record independent selected-store capability acceptance --- ...ments-only-to-the-selected-storage-path.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md index f449da3..46b8c22 100644 --- a/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md +++ b/.github/ISSUE_SPECS/102-apply-opfs-capability-requirements-only-to-the-selected-storage-path.md @@ -83,3 +83,40 @@ package policy, and whitespace checks PASS. Root checkpoints the four intended source/test/README paths; no release pins changed. Independent fresh Astra MEDIUM verification and required merged CI remain pending. This is attempt 1; no published capability or final acceptance is claimed yet. + + +## Independent attempt 1 review — 2026-09-14 + +Fresh Astra MEDIUM adversarial review of implementation +`6616d01b7560d35975ad0b9733f97e7f76cfe708`: **PASS for the frozen correction**. +No production or test fixes were needed. Both entry points retain their supplied +store and selected-path fact before awaiting work; runtime predicates always +run, default persistence predicates precede scratch/source work, and supplied +stores retain their actual backend checks and error propagation. The helpers +remain module exports, without a new package API or backend protocol. + +Independent local evidence, Node 22.23.2/npm 10.9.8: + +- Focused session tests: 45/45 PASS. +- `npm run check`: 367/367 PASS, including reproducible SHA256 Wasm, + source/decoder/package policies and type checks. +- Fourteen review probes: all six runtime refusals precede scratch/store work + for both entry points; mutating `options.store` during deferred scratch work + preserves the original supplied store and its exact typed backend refusal. +- Existing packed default browser fixture: PASS on Chromium 151.0.7922.34, + including cold/warm ingestion, Worker/AudioWorklet assets and no console or + request failures. +- Existing packed OPFS gate: Chromium PASS (65,536 cold bytes, warm reuse, + sparse verification and cleanup). Linux Playwright WebKit 26.5 is an + environment blocker: its fixture throws `ReferenceError` for the absent + `FileSystemFileHandle`. A separate blank localhost page, secure and + cross-origin isolated, confirms both `FileSystemFileHandle` and + `navigator.storage.getDirectory` are undefined without loading the adapter. + This is **not a passing WebKit gate**; required macOS OPFS CI remains pending. +- Implementation whitespace check: PASS. + +Raw logs and the repeatable review probe are preserved outside this worktree in +`/tmp/miso-796-audit/adapter102-{focused,check,adversarial,packed-browser,packed-opfs,webkit-environment}.log` +and `adapter102-adversarial.mjs`. The coordinator owns evidence checkpointing, +merged required CI, GitHub synchronization and ordinary package release. This +review does not claim merged delivery, issue completion or registry availability.