Repository navigation
feat: add large-file streaming, recoverable pointers and manifest exports - #7
Conversation
Address the PR #7 review findings. Pointer payments: - Persist the payment intent (signed state, plan, quotes) in one write immediately before the wallet is invoked. PointerError carries a checkpoint only once a wallet may have been asked to pay. - Resolve a write the network stored even if a later checkpoint write failed or timed out. - Accept a receipt only after Rust validates it against the plan; keep a rejected receipt's hashes as evidence so resume observes it again. - Journal only known, JSON-safe payment fields from wallets. - Confirm broadcasts through submission wait() in the write, and let resumePointer reuse this page's wait() observers before recover(). - Serialize and coalesce checkpoint writes; store paid bytes as base64. Client lifecycle: - Lease the WASM network handle for every raw call. close() shuts the transport immediately, except while a paid pointer write is storing, and frees the handle once no call borrows it. Downloads and saves: - Default buffered downloads to a 1 GB maxMemoryBytes, the former cap. - Report destination write/close failures from pipeTo as SAVE_FAILED. - Normalize streamAndSave sources and never use an empty file name. - Share one save-picker helper; remove the empty file a picker created when a save fails before writing. - Report streamAndSave capability for its default picker path. - Keep the example's late finally from clearing a newer download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Provisional review — a4b6a537934190e744014b5652704e2388291516
No new blocking code defect confirmed in the reviewed paths. This is not an approval or full-team consensus: only 1/3 named models completed substantive reviews. Keep the existing draft/release gates in place.
Verified locally
- The SDK build, 248 tests in 23 files, and all five example builds passed. The tests include the packaged Rust/WASM manifest and paid-pointer proof boundary; these are Node tests, not a fresh browser-to-node integration run.
- A further focused run of pointer, reader and client regressions passed 100 tests. It exercises observation-only recovery, failed checkpoint persistence, cancellation, destination failures and empty-file cleanup.
- ADR governance and the 52 PR-checker cases passed. Package-content inspection passed with
npm pack --dry-run --ignore-scripts --json; this deliberately bypassed prepack and does not mean the normal packaging gate passed. - Local
npm run checkstops attest/wasm.test.ts:16:46:Buffer<ArrayBufferLike>is not assignable toBufferSourceinWebAssembly.compile. The detached base29ab1758e1188fc445b82465949dcc655345935bfails at the same unchanged call (base line 14) under the same installed dependencies. The npm lockfile is identical on base and head. This is an inherited local toolchain/type-check failure, not a newly introduced PR defect. GitHub's checks for this head report success.
Gates still open
- Companion/provenance: WithAutonomi/ant-client#230 remains open at
8316468b7f5f813457f195bccb16a452426d5f35. I rannpm run verify:wasm; it rejects that revision because it is not on ant-client main. As the PR already states, merge the companion, rebuild and commit WASM from main, then rerun provenance and SDK checks. This is disclosed release choreography, not a new code finding. - Current-head browser validation: the author explicitly says the real-browser devnet run predates the final pointer-journal/recovery changes. Rerun those browser-to-node flows on the release candidate, including cancellation and recovery after a lost confirmation. I did not start a network or execute paid operations. The earlier >4 GiB test is author-reported evidence from an earlier revision, not independently repeated here.
- Review coverage: complete the missing named-model reviews before treating this as the required full panel. Proposed ADRs still need human engineering judgement.
Panel participation and dissent
Two roles were requested over each of the pointer/manifest and streaming/lifecycle scopes. Qwen 3.8 completed four scope/role reviews; GLM 5.3 produced incomplete responses or exceeded its deadline, and DS4 timed out. 4/12 scope/role receipts completed, all from the same model; this is 1/3 models reviewed, not four independent votes. No substitute model was counted.
Qwen returned changes-required opinions about checkpoint persistence and, in its adversarial streaming pass, cleanup. I checked those claims against the SDK, the frozen companion Rust source and the regression tests:
- The claimed automatic repeat payment is not supported:
resumePointer()uses observation-onlyrecover()or the retainedwait()observer andstorePaidPointer(); it never callspay(). The initial intent is persisted before invoking the wallet. Rust deliberately stores an already-paid state even if later checkpoint persistence fails or times out. The README explicitly documents this trade-off and requires external reconciliation when a crash hid a transaction hash; recovery does not promise automatic resolution of that case. - The partial-file deletion claim omits the
getFile().size === 0guard and the wait for destination release. The tests verify that a non-empty picked file is retained. The stream-abort claim also does not establish a swallowed internal error: with no external abort, the catch path callscontroller.error(error); with external abort, that listener has already errored the stream.
I therefore have not promoted those claims to confirmed code defects. The model's dissent is recorded here; the failed seats are unavailable, not agreement. Outcome: provisional COMMENT only; no merge, deployment or release approval.
Rebuild the bundled WASM from ant-client 973ae938c77863f52164eeb5c93c615e5d6e45e6
(same rustc 1.99.0, notices and std copyright as the previous bundle),
which addresses that PR's review:
- Pointer writes pass { onPrepared, onPaid } as the core's trailing
options object instead of two positional callbacks.
- recoverPointerPayment now needs the transaction named by quote hash.
A wallet that returns one transactionHash paid every journaled quote,
so the SDK keys it by the journal's quotes; a split receipt is passed
through unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuild the bundled WASM from WithAutonomi/ant-client#230 at bab6beb with the same rustc 1.99.0, notices and std copyright. - The core's pointer checkpoint now holds its record in base64url and its plan in base64url MessagePack. Re-encode the devnet fixture's core checkpoint into that layout from the same signed record and plan; the receipt and expected paid state are as captured. Rewrite the tamper test for the new layout, and check that the untouched checkpoint recovers, so each refusal is caused by its edit. - recoverPointerPayment now takes a single transactionHash for the plan's quotes, as the write does, so pass receipts through instead of keying them in the SDK. Cover both forms against the real devnet checkpoint. - Document that storers refuse a paid proof once its quotes are a day old. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuild the bundled WASM from WithAutonomi/ant-client#230 at 4d27758 with the same rustc 1.99.0, notices and std copyright. The JS API is unchanged; only doc comments and export order differ. - storePaidPointer no longer replaces a storer's refusal of a day-old proof with "paid proof expired": it reports the storer's error, noting when this device's clock puts a quote past the age storers accept. Document that instead. - The core's onSubmission bounds and pointer write errors need nothing here: the SDK's pay function does not call the core's onSubmission, and nothing matches "payment was not submitted". createManifest now refuses an intermediate shrunk DataMap, which no upload returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuild the bundled WASM from WithAutonomi/ant-client#230 at 404fa34, now rebased onto main, with the same rustc 1.99.0 and std copyright. The rebase brings ant-protocol 5.0.0 from crates.io instead of the pinned 4.0.0 revision, saorsa-core 0.29.1 and saorsa-transport 0.38.1; the notices follow. The JS bindings change only in export order. Manifest entries may now record the creator's SHA-256 of the plaintext (ant-client #229, ADR-0006), and createManifest accepts one as hex. - ManifestEntry carries it as optional hex `sha256`; the SDK dropped the field the core returns, so CLI manifests lost it when read here. - ManifestEntryInput takes an optional `sha256`, which Rust validates. - The SDK surfaces the hash and does not check it: the CLI checks it on extraction, which the browser has none of, and downloads are verified by content address. ADR-0007 and the README say so. - Tests decode ant-client's golden fixture with a sha256, carry one through createManifest, decode and link, and refuse malformed ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnaling download() and downloadAndSave() hold a manifest entry's bytes to the sha256 its creator recorded, as the CLI does when extracting. A mismatch fails with DOWNLOAD_FAILED before anything is returned or saved, and a malformed sha256 is refused with INVALID_SOURCE before any fetch. WebCrypto hashes the bytes, since the core's browser build exports no SHA-256, and does so before the Blob copy so peak memory does not rise. Without WebCrypto such an entry fails rather than arriving unchecked. Streaming saves, readers and media sources do not check it. PaymentContext.submitted() now returns a promise that settles once the broadcast is journaled, in the pointer checkpoint or the core's upload journal, or after at most 30 seconds, and never rejects, matching the core's onSubmission. The Ethers and Wagmi providers await it before observing the receipt. The return type widens to void | Promise<void>, so existing contexts still compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real Chromium against seven devnet nodes and local Anvil, with the WASM from ant-client 404fa34: manifest sha256 checks on download and save, with native CLI parity; wallets waiting for the journal; pointer writes, same-page confirmation and recovery of a lost page without a second payment. The 2026-10-15 audit's recovery finding now links here, since a same-page broadcast no longer fails the write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WithAutonomi/ant-client#230 merged as 8b19c14. The bundle now carries its unknown-key check on any JavaScript object (93f3bd2), the hot-record read spreading of #225, and main's testnet pin of saorsa-transport to git revision 520dd3e, which the licence notices record. The SDK checks, verify:wasm and the real-browser devnet run pass; the audit records them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ant-client main released 0.14.0, which removed the testnet git pins, and took two dependency updates for RustSec advisories. No browser source changed. The bundle now builds every crate from crates.io, including saorsa-transport 0.38.2 in place of git revision 520dd3e; the JS bindings are unchanged. The SDK checks, verify:wasm and the real-browser devnet run pass; the audit records them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
createMediaSource() opened its reader in the core's streaming mode, which starts fetching ahead, and the file's last record, alongside the first block a player waits for. On the live network that delayed the first frame. Media sources now open an ordinary reader, which still reads ahead once playback's reads follow each other, and keep priming the head and tail. The media service worker installs while the reader opens, and priming starts as soon as the reader is open rather than once the worker controls the page; errors surface in the same order as before. While any media source is open the page messages the worker every 10 s, because Firefox stops a worker that has had no new event for 30 s, even one still answering a request. The worker waits up to 120 s for a block, beyond the core's retries of a missing record after 15 and 45 s, and marks its responses nosniff and sandboxed, since their type comes from the file's metadata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ten alternating live rounds of createMediaSource() with try's featured video, a real-Firefox check of the worker keep-awake with lowered idle limits, playback through the packaged worker in Chromium, Firefox and WebKit, and the SDK checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first-frame evidence came from one ten-round batch. Two later batches of the same builds kept the plain reader ahead by less (7.8 s against 9.7 s, then 8.8 s against 9.2 s), and in the later one it also sought faster, so the seek trade-off the first batch suggested is withdrawn. The audit also records that the release bundles the ccd4106 WASM, whose browser and read source is unchanged from the 8b19c14 build measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the same ccd4106 core, video and browser, createMediaSource() started the video as soon as try's own player: a median first frame of 4.5 s against 4.1 s over 20 rounds, sooner in 9 of them. A traced run shows both pages doing the same network work, one lookup and about eight GETs for the first record, and the first frame following that record by 47 and 75 ms. The same run found no first-frame gain from the plain reader over streaming mode (6.1 s against 5.6 s over 12 rounds), so the earlier audit's claim that it led every batch, and ADR-0003's, are corrected: the reader mode is kept because it does no worse, fetches less ahead of the player, and matches try's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release workflow published only when someone pushed a v<version> tag, so merging a version change released nothing until that tag followed. It now also runs on pushes to main: if package.json holds a version with no v<version> tag, it runs the same checks, publishes that version and creates the tag on the merge commit with the GitHub release. A push whose version is already tagged releases nothing and skips the checks, so merges that do not change the version are unaffected. Pushed tags still publish, and still have to match package.json and be on main. A first job decides what a run does. The release step names the tag from package.json rather than the ref, which is "main" after a merge; a tag the workflow creates with its own token starts no further run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaidSafe-Hermes
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES
Coverage: 2/2 review seats completed across seven focused scopes (sol-1 / gpt-6.1-sol xhigh; glm-1 / z-ai/glm-5.3). Both seats independently found the release-workflow defect. I checked the findings against the exact PR head and local execution.
Required changes
-
Keep manual runs from publishing (
.github/workflows/release.yml:50-69, 150). Aworkflow_dispatchrun on a matchingv<version>tag enters the tag branch and setspublish=true; the publish job then gates only on that output. This contradicts the workflow's dry-run promise and can publish to npm and create a GitHub release. Requiregithub.event_name == 'push'in the publish gate, or only setpublish=truefor push events. Add a regression check for manual dispatch on both branch and tag refs. GitHub permits branch or tag refs for workflow dispatch. -
Copy caller-owned seed bytes before zeroizing (
src/pointers.ts:81, 90, 150, 226).ownerSeedis aUint8Array, which includes NodeBuffer.Buffer.slice()shares the original backing store, so thefinallyblock'sseed.fill(0)also clears the caller's seed afterpointerAddress()or a pointer write. Replace bothslice()calls with an independent copy (for example,new Uint8Array(ownerSeed)`) and test that Buffer inputs stay unchanged on success and failure. -
Do not delete an existing empty file selected in the save picker (
src/save.ts:130-133;src/client.ts:702-704, 761-762). On a download/open failure before the SDK writes, cleanup treatsgetFile().size === 0as proof that the picker created the file and callsremove()`. A user can select an existing empty file, which this path then deletes. Only remove a file when the SDK can establish that it created it; otherwise leave the selected file in place. Add failure tests with a pre-existing empty handle for both save paths. -
Make the close-before-payment test await the async continuation (
test/client.test.ts:1663-1665).release()resolves the pending checkpoint, but the immediatepayment.payassertion runs before the callback resumes. Await the raw operation settling (for example,raw.freed`) before asserting no payment call, so the test detects a later wallet invocation.
Review notes
I did not treat the missing manifest SHA-256 check in streamAndSave() as a defect: the README and ADR-0007 state that streaming reads do not check this hash; buffered downloads do. The generated bindings and WASM provenance were verified, and the saved test report passed. The local npm run check stops at an existing TypeScript BufferSource error in test/wasm.test.ts; the base revision fails at the same call. The GitHub checks pass on this PR head.
A save that failed before writing removed the picked file whenever it was empty, taking that as proof the save picker had just created it. A user can pick an existing empty file, which was then deleted. The SDK cannot tell the two apart, so a failed save now deletes neither and leaves the file the user chose where it is, possibly empty. The pipe settlement tracking existed only for that removal and goes with it. The tests now pick an empty and a non-empty existing file on both save paths and check that neither is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The close-before-payment test asserted that no wallet call was made just after resolving the checkpoint the write paused on, before the write's continuation could run. Closing had already freed the client's handle, so waiting for that freed nothing either. The test now lets everything chained to the checkpoint run first, so a wallet call made once it lands fails it; a deliberate call of that kind went unnoticed before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pointerAddress() and pointer writes took the seed copy they zero afterwards with ownerSeed.slice(). A Node Buffer is a Uint8Array whose slice() shares its bytes, so zeroing that copy also cleared the caller's seed. Both now copy the bytes with new Uint8Array(seed). Tests pass Buffer seeds to pointerAddress() and to a pointer write, on success and on failure, and check the caller's seed is unchanged while the copy handed to Rust is still zeroed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A manual run started on a v<version> tag took the pushed-tag path, decided to publish, and the publish job, which since the release-on-merge change checked only that decision, would have published to npm and created a GitHub release. Only a push now decides to publish, and the publish job again also requires a push event. The decision moves from the workflow into scripts/release-decision.sh so test/release-decision.test.ts can run it against a scratch repository: merges with and without a new version, pushed tags on and off main or not matching package.json, manual runs on a branch and on a release tag, pre-release dist-tags, and the publish job's push gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. All four requested changes are in, one commit each, on top of
🤖 Generated with Claude Code |
MaidSafe-Hermes
left a comment
There was a problem hiding this comment.
APPROVE — re-reviewed exact head 802db874c67d38ca9b6cb6ee48b39d859ff3dce2.
Both seats agree (two model families): 2/2 seats reviewed (sol-1 gpt-6.1-sol xhigh; glm-1 z-ai/glm-5.3). The four findings from the prior review are fixed:
- Manual dispatch cannot publish: the decision script only enables publishing for
push, and the publish job independently requirespush. - Pointer seed handling now copies the bytes before zeroing the SDK-owned seed, so a caller’s Buffer remains unchanged.
- Failed save paths no longer delete the picker-selected file; stream abort behavior remains.
- The close-before-payment test releases the checkpoint and waits a timer turn before asserting that payment was not called.
No new blocking issue was found in the follow-up diff. Verified on this head: build passed within npm run check; all 272 tests in 46 suites passed; WASM provenance verification passed; all five GitHub checks passed. The local npm run check then stops at the known test/wasm.test.ts BufferSource type error, which was also present on the base revision; the hosted check for this head is green.
Bring the browser SDK up to date with ant-client's large-file readers and expose typed pointers, manifest exports and bounded streaming saves. Files larger than browser memory can be saved through
streamAndSave()orPublicFileReader.pipeTo()into a disk-backed destination. Streaming reads and media no longer have a fixed 1 GB cap.download()anddownloadAndSave()retain their buffered bytes/Blob contract and keep the former 1 GB limit as their defaultmaxMemoryBytes; browser uploads remain capped at 1 GB.The companion WithAutonomi/ant-client#230 merged into main. The bundled WASM is a clean build of the latest main,
ccd4106dd8fd60545bbd31c40d46cc041f85fb8d(ant-client 0.14.0), every bundled crate from crates.io, andnpm run verify:wasmpasses. This PR sets the version to 0.1.3, and merging it releases 0.1.3 (below).The change also adds configurable download memory budgets, cancellation and stream cleanup; typed pointer read/resolve/create/update/transfer/finality with durable, observation-only payment recovery; and Rust-backed
.antexports and compact share links. Media sources now start playback as soon as try.autonomi.com's own player does. Documentation, the all-in-one demo, proposed ADRs and dated audits cover the new behavior.Review follow-up (
a4b6a53) addresses the 15 code-review findings:PointerError.checkpointis set only once a wallet may have been asked to pay. A write the network stored resolves even if a later checkpoint write failed. Receipts are journaled only after Rust validates them against the plan. Wallet fields are copied JSON-safe. Broadcasts are confirmed throughwait(), in the write and on resume from the same page. Checkpoint writes are serialized and coalesced, with paid bytes in base64.close()shuts the transport immediately, unless a paid pointer write is still storing, and frees the handle once no call borrows it.SAVE_FAILED, read failures withOPEN_FILE_FAILED.streamAndSavenormalizes its source and never uses an empty file name. It shares one picker helper and removes an empty picker-created file when a save fails before writing;downloadAndSavedoes the same. ThestreamAndSavecapability reports the picker path, and the demo's disk-download controls no longer race.Companion follow-up (
ff2136b) adopts the review fixes in WithAutonomi/ant-client#230 (973ae93) and rebuilds the bundled WASM from it with the same rustc 1.99.0, notices and std copyright:{ onPrepared, onPaid }as the core's trailing options object instead of two positional callbacks.transactionHashpaid every quote this journal recorded, so the SDK keys it by those quotes before calling Rust; a split receipt is passed through unchanged.PaymentProvideris unchanged.onPreparedstop a write before payment.Second companion follow-up (
12957cd) adopts WithAutonomi/ant-client#230's second review follow-up (bab6beb, breaking for the core's own API) and rebuilds the bundled WASM from it with the same rustc 1.99.0, notices and std copyright. The SDK's public API is unchanged:prepared) now holds the record in base64url and the plan in base64url MessagePack (87,821 instead of 156,821 characters for the devnet fixture). The devnet fixture's core checkpoint is re-encoded into that layout from the same signed record and plan, with the code below; its receipt and expected paid state are as captured. The tamper test is rewritten for the new layout and now also checks the untouched checkpoint recovers, so each refusal (version, signature, plan size, plan address) comes from its edit.recoverPointerPaymentnow takes onetransactionHashfor the plan's quotes, as the write does, so the SDK passes receipts through instead of keying them itself. Both forms are checked against the real devnet checkpoint.{ onPrepared, onPaid }, and itsonPreparedsettles at once, so the core's new 30-secondonPreparedlimit and refusal of the bare-function form change nothing. It keeps its own submission journal: the SDK's payment function is the wallet the core calls, so routing its broadcasts through the core's newonSubmissionwould only hand them back, and the journal also holds thewait()observers recovery uses. Manifest callers send only declared keys, which the core now enforces.Checkpoint re-encoder (ant-core at
bab6beb, with itsant-protocolpatch andCargo.lock)Built with ant-client's
Cargo.lock(samermp-serde 1.3.1,serde_json 1.0.150,base64 0.22.1). It writes whatPointerPaymentCheckpoint::encodewrites atbab6beb. Onlypreparedintest/fixtures/pointer-payment-devnet.json.gzwas replaced.Third companion follow-up (
b69bbbb) rebuilds the bundled WASM from WithAutonomi/ant-client#230 at4d27758(1f2534band its real-node test, before #230's rebase onto main) with the same rustc 1.99.0, notices and std copyright. The JS API and the core's checkpoint layout are unchanged:storePaidPointerno longer replaces a storer's refusal of a proof whose quotes are over a day old with "paid proof expired". It reports the storer's error, noting when this device's clock puts a quote past the age storers accept. The README now says so.onSubmissionis now time-bounded and never fails the wallet, and a write waits for the broadcasts it reported to be kept; the SDK's payment function does not call the core'sonSubmission, so neither applies. Every failure before the wallet now ends "payment was not submitted", which nothing in the SDK matches on.createManifestrefuses a shrunk DataMap of more than three chunks, an intermediate level no upload returns.Fourth companion follow-up (
b44ebfb) rebuilds the bundled WASM from WithAutonomi/ant-client#230 at404fa34, now rebased onto main, with the same rustc 1.99.0 and std copyright. The pointer commits came through the rebase unchanged, so pointer behavior and the checkpoint layout are as inb69bbbb:404fa34letscreateManifesttake one.ManifestEntrynow carries it as optional hexsha256; the SDK dropped the field before, so manifests from the current CLI lost it when read here.ManifestEntryInputtakes an optionalsha256, which Rust validates.317a634, below): the SDK surfacessha256and does not check it. The CLI checks it on extraction, which the browser has none of; downloads and readers are verified by content address, and a ranged reader never holds the whole file. ADR-0007 and the README say so, and that an application wanting the claim checked hashes what it downloads.ant-protocol 5.0.0from crates.io instead of the pinned4.0.0revision,saorsa-core 0.29.1andsaorsa-transport 0.38.1; the notices follow. The JS bindings change only in export order.SDK follow-up (
317a634) takes up two open items. The bundled WASM is unchanged (404fa34):download()anddownloadAndSave()hold a manifest entry's bytes to itssha256before returning or saving them, as the CLI does when extracting. A mismatch fails withDOWNLOAD_FAILEDnaming both hashes, anddownloadAndSave()writes nothing; a malformedsha256is refused withINVALID_SOURCEbefore anything is fetched. WebCrypto hashes the bytes because the core's browser build exports no SHA-256; this is the one check outside Rust, and ADR-0007 records why. It hashes before the result's Blob copies the bytes, so peak memory does not rise. Outside a secure context, where WebCrypto is unavailable, such an entry fails to download rather than arriving unchecked.streamAndSave(),openFile()andcreateMediaSource()never hold the whole file and do not check it; the README says so.PaymentContext.submitted()now returns a promise that settles once the broadcast is durable, in the pointer checkpoint or the core's upload journal, or after at most 30 seconds, and never rejects, as the core'sonSubmissiondoes for its own wallets. The bundled Ethers and Wagmi providers await it before observing the receipt, and the README's custom-provider example and ADR-0009 tell other wallets to. Its type widens fromvoidtovoid | Promise<void>, so existingsubmitted() {}implementations still compile, and a wallet that does not await it behaves as before.Main sync (
65a8ce0,84eace2,8c00e82) records the real-browser devnet run of317a634, rebuilds the WASM from main8b19c14with the same rustc 1.99.0 and std copyright, and sets the version to 0.1.3:317a634(audit): the first real-browser run sincea4b6a53. It covers the manifestsha256checks, with native CLI parity, and wallets waiting for the journal. It also covers a write that now confirms a same-page broadcast itself, and recovery of a lost page without a second payment. The 2026-10-15 audit's recovery finding links to it.93f3bd2checks unknown keys on any JavaScript object, which the SDK's own option objects andstructuredClonecopies already satisfied. #225 spreads hot-record reads across the close group. Main's[patch.crates-io](a205d06, for ADR-0023 testnet builds, to be removed once the crates are published) pinssaorsa-transportto git revision520dd3e. That crate is a normal dependency of the browser build, so the bundle carries it instead of the crates.io0.38.1, and the licence notices record the git source. The pinnedant-nodeis only a dev-dependency and is not bundled. The JS bindings' exports and the SDK's public API are unchanged.26129a2, which main's browser tests still use.Second main sync (
56f4f6d) rebuilds the WASM from mainccd4106(audit). Main released 0.14.0, which removed the testnet git pins, and took two dependency updates for RustSec advisories (WithAutonomi/ant-client#233, #236); no browser source changed. The bundle now builds every crate from crates.io:saorsa-transport0.38.2 instead of git revision520dd3e,ant-protocol5.0.1 andsaorsa-core0.29.2. The JS bindings are byte-identical. The earlier caveat that the release ships an unreleased transport revision no longer applies.Media first frame (
7b5568a,f177ff4,d06c176; first proposed separately as #8) brings into the SDK what try.autonomi.com does differently when it starts a video. The bundled WASM is unchanged:createMediaSource()opens its reader without the core's streaming mode. That mode fetched up to four records of read-ahead, and the file's last record, alongside the first block a player waits for. The plain reader still reads ahead once reads follow each other, and head and tail priming stays.nosniffand sandboxed, since their type comes from the file's metadata.ccd4106core in both,createMediaSource()started the video as soon as try's own player: a median first frame of 4.5 s against 4.1 s over 20 rounds, sooner in 9 of them (comparison). A traced run shows both pages doing the same network work, one lookup and about eight GETs for the first record, with the first frame following that record by 47 and 75 ms. The reader mode alone gave no measurable gain: the plain reader led the first three batches by 0.4 to 2.5 s at the median, but not a fourth (6.1 s against 5.6 s over 12 rounds), so none is claimed (audit). It is kept because it does no worse, fetches less ahead of the player, and matches try's. No seek difference is claimed. Most of the remaining time is the core finding and connecting to the first record's holder (medians of 1.4 s and 0.6 s), which no SDK change affects.Release on merge (
bb38da8): merging this PR publishes 0.1.3. Releases used to need av<version>tag pushed after the merge.package.jsonholds a version with nov<version>tag, it runs the same checks (WASM provenance,npm run check, example builds, pack), publishes that version to npm with provenance, and creates the tag on the merge commit along with the GitHub release.package.jsonand be on main. A manual run, on a branch or a tag, only checks and packs (fixed in802db87). The tag the workflow creates with its own token starts no second run.Review follow-up (
3e6daec,084e0b9,6c2f7ef,802db87) makes the four changes requested atbb38da8:802db87): a manual run on av<version>tag decided to publish, and the publish job checked only that decision. Only a push decides to publish now, and the publish job again also requires a push event. The decision moved intoscripts/release-decision.sh, whichtest/release-decision.test.tsruns against a scratch repository, sonpm run checkcovers it in CI and in the release itself.6c2f7ef): a NodeBuffer'sslice()shares its bytes, so zeroing the SDK's copy cleared the caller's seed afterpointerAddress()or a pointer write. Both copy withnew Uint8Array(seed).3e6daec): removing an empty picked file could delete an existing empty file the user chose, and the SDK cannot tell that from one its picker created. A failed save now deletes neither. This reverses the removal described in thea4b6a53follow-up and checked in the65a8ce0devnet run above.084e0b9): it checked for a wallet call before the paused write could continue. Closing frees the handle at once, so waiting for that was no help either; the test now lets the checkpoint's continuation run first.The provisional panel review at
a4b6a53predates the companion follow-ups.Linear issue
Closes V2-1398
Closes V2-1478
Closes V2-1354
SDK follow-ups to the existing large-file, manifest and pointer work tracked by ant-client PRs WithAutonomi/ant-client#214, WithAutonomi/ant-client#219 and WithAutonomi/ant-client#210 respectively.
Risk tier
Proposed for human confirmation: browser lifecycle and paid-pointer recovery behavior change. Protocol validation, quote verification, cryptography, payment planning and network policy remain in Rust, except that WebCrypto checks a manifest entry's creator-recorded
sha256on download (ADR-0007).Compatibility
26129a2611cac08f9f53a557f91fb8806b40a1cc, and ant-client#230 exercised404fa34against the same nodes. The release bundle, built from mainccd4106with every crate from crates.io, passed the real-browser run against the same nodes. No broad mixed-fleet compatibility claim.sha256ant-client added within v1, which older readers ignore. Adds versioned (v1) application-owned pointer recovery checkpoints. They hold the payment intent (original signed state, payment plan, quotes and network), broadcast evidence and, once Rust accepts the receipt, the receipt with the paid record and proof in base64; never the owner seed. Checkpoints captured on the devnet beforea4b6a53use an earlier unreleased layout and do not resume; checkpoints written before12957cdhold the core's earlier unreleased checkpoint layout and do not resume either. Existing upload checkpoints are unchanged.POINTER_FAILEDerrors.streamAndSaveandpipeToreturn byte counts/hashes without a complete Blob;download/downloadAndSaveretain their existing results and ordinary save fallback, and default to a 1 GBmaxMemoryBytes(SDK_LIMITS.defaultDownloadMemoryBytes, the former cap); callers can pass another budget. Destination write failures inpipeTo/streamAndSavereject withSAVE_FAILED, read failures withOPEN_FILE_FAILED. ThestreamAndSavecapability describes its default save-picker path.maxFileBytesremains the compatible upload-limit alias; media positions support exact JavaScript integers. Pointer writes require durableonCheckpointpersistence, preserve decimal counters and all five finality statuses, and resume without invoking a fresh payment.PointerError.checkpointis undefined when no payment was requested. Manifest entries andcreateManifestentry inputs gain an optional hexsha256, whichdownload/downloadAndSavecheck, failing withDOWNLOAD_FAILEDon a mismatch or without WebCrypto.PaymentContext.submittedreturnsvoid | Promise<void>; wallets should await it before observing the receipt. Deploy the updated media service worker alongside the SDK; applications that merged the packaged worker into their own should also merge its newmessagelistener.Semver impact
Proposed for human confirmation; the existing public download/save contract is preserved. The maintainer chose to release it as 0.1.3.
Test evidence
Pointer, manifest and streaming-save audit and large-file audit include exact revisions, commands, scripts, hashes and limitations.
802db87: SDK build, TypeScript and 272 tests in 24 files passed, as did all five example builds,npm pack --dry-run(97 files; no scripts or tests packed),npm run verify:wasm, ADR governance (8 ADRs), all 52 PR-checker cases, and a parse ofrelease.yml. Each new or changed test fails with the defect put back:slice()again fails the three Buffer tests (pointerAddress(), and a pointer write that is stored and one that fails).bb38da8: the workflow parses (three jobs: decide, package, publish). Its decision script, run against a clone of this repository with each event's environment: a push to main at 0.1.2 (already tagged) releases nothing; this PR's head at 0.1.3, pushed to main, publishes tolatest; av0.1.3tag pushed before the merge is refused as not on main; a tag that does not matchpackage.jsonis refused; a manual run checks without publishing; a-beta.1version goes tobeta; an unknown pre-release fails before publishing; and oncev0.1.3exists, a re-run releases nothing. The publish path itself runs for the first time on this merge. SDK build, TypeScript and 262 tests, ADR governance and all 52 PR-checker cases passed.d06c176(WASM unchanged, from ant-client mainccd4106): SDK build, TypeScript and 262 tests in 23 files passed. New tests cover the worker installing while the reader opens with priming before it is ready, a plain reader, a failed worker surfacing once the reader has opened and closing it, the bridge doing nothing more once its worker controls the page, keep-awake messages every 10 s only while a source is open, and the worker's listener, timeout and headers; five fail against the previous source. All five example builds,npm pack --dry-run(97 files, including the updated worker),npm run verify:wasm, ADR governance (8 ADRs) and all 52 PR-checker cases passed. In Firefox with its worker idle limits lowered to 15 s and 1 s, the packaged worker answered a block that took 22 s, where the previous worker was stopped mid-answer; a test-pattern MP4 played through the bridge in Chromium, Firefox and WebKit. Live timings: the first three batches used the8b19c14WASM, whose browser and read sourceccd4106leaves unchanged; the side-by-side comparison with try.autonomi.com (637eba0, comparison) rebuilt every variant on theccd4106core, 12 rounds plus 8 traced, Chromium 151, with no failed sample or page error. The real-browser devnet run at56f4f6dhas not been repeated with this change.56f4f6d(WASM from ant-client mainccd4106, version 0.1.3):npm run verify:wasmpassed. SDK build, TypeScript and 258 tests in 23 files passed, as did all five example builds,npm pack --dry-run(97 files), ADR governance (8 ADRs) and all 52 PR-checker cases. The real-browser devnet run passed on its first attempt with the same scripts: seven WebRTC nodes at26129a2and local Anvil, Chromium 151, and the CLI built fromccd4106for native parity.84eace2and8c00e82(WASM from ant-client main8b19c14, version 0.1.3):npm run verify:wasmpassed. SDK build, TypeScript and 258 tests in 23 files passed. All five example builds passed, as didnpm pack --dry-run(withautonomi-ant-browser-sdk-0.1.3.tgz, 97 files), ADR governance (8 ADRs) and all 52 PR-checker cases. The real-browser devnet run passed on its first attempt: seven WebRTC nodes at26129a2and local Anvil, Chromium 151, and the CLI built from8b19c14for native parity.65a8ce0of317a634(WASM from404fa34): real Chromium against seven WebRTC nodes at26129a2and local Anvil passed every check.download()anddownloadAndSave()held public and embedded entries to theirsha256: matching bytes were returned or saved; a wrong claim failed withDOWNLOAD_FAILED, no writable opened, and the empty picked file was removed.streamAndSave()ignored the claim, as documented. The native CLI decoded the browser's manifests and itsmanifest downloadagreed on both outcomes. With a journal that took 1.5 s, the Ethers wallet resumed only once the broadcast was persisted, for both uploads and a pointer write. Pointer create, update, transfer, finality (7/7) and paid replay passed. A write whose wallet threw after a real transaction confirmed it on the same page. A page lost after its broadcast was journaled was recovered by a fresh page, with one payment in total. Earlier attempts stopped on out-of-date script steps and once on a WebRTC dial lost during a pointer store; the audit records each.317a634(WASM unchanged, from ant-client404fa34): SDK build, TypeScript and 258 tests in 23 files passed. New tests:download()returns a manifest entry's bytes only when they match itssha256(given in uppercase), fails withDOWNLOAD_FAILEDon a mismatch or without WebCrypto, and refuses a malformed value withINVALID_SOURCEbefore any fetch;downloadAndSave()opens no writable on a mismatch; upload and pointer wallets resume only after the journal write settles, whether it is kept or fails; the Ethers and Wagmi providers do not observe a receipt untilsubmitted()settles; the wait is bounded at 30 seconds and never rejects. All seven behavior tests fail with the source changes reverted. All five example builds,npm pack --dry-run(97 files), ADR governance (8 ADRs) and all 52 PR-checker cases passed. The real-browser devnet run has not been repeated.b44ebfb(WASM from ant-client404fa34): SDK build, TypeScript and 250 tests in 23 files passed. They include ant-client's golden fixture with asha256, asha256carried throughcreateManifest, decoding and its link, malformed values refused, and the re-encoded real devnet checkpoint still recovered and resumed; bothsha256pass-through checks fail without thesrc/manifest.tschange. All five example builds,npm pack --dry-run(97 files), ADR governance (8 ADRs) and all 52 PR-checker cases passed.npm run verify:wasmfails only because404fa34is not on ant-client main. The real-browser devnet run has not been repeated against this bundle.b69bbbb(WASM from ant-client4d27758): SDK build, TypeScript and 249 tests in 23 files passed, including the re-encoded real devnet checkpoint, still recovered from both receipt forms and resumed. All five example builds,npm pack --dry-run(97 files) and ADR governance (8 ADRs) passed.npm run verify:wasmfails only because4d27758is not on ant-client main. The real-browser devnet run has not been repeated against this bundle.12957cd(WASM from ant-clientbab6beb): SDK build, TypeScript and 249 tests in 23 files passed, including recovery of the re-encoded real devnet checkpoint from both a keyed and a single-hash receipt, its resume and exact-proof storage, and the rewritten tamper test. All five example builds,npm pack --dry-run(97 files) and ADR governance (8 ADRs) passed.npm run verify:wasmfails only becausebab6bebis not on ant-client main. The real-browser devnet run has not been repeated against this bundle.ff2136b: SDK build, TypeScript and 249 tests in 23 files passed. These include a new check that single-hash receipts reach Rust keyed by the journaled quote, and the real devnet checkpoint recovered and stored through the rebuilt core. All five example builds passed.npm run verify:wasmfails only because973ae93is not on ant-client main.a4b6a53: SDK build, TypeScript and 248 tests in 23 files passed. New regressions cover each review finding, plus a production-WASM resume of the real devnet checkpoint that stores the exact paid proof through the base64 journal. All five example builds,npm pack --dry-run(97 files), ADR governance and all 52 PR-checker cases passed. The real-browser devnet run below predates this commit; re-run it before merge, because the pointer journal and recovery flow changed.d84ec49: final SDK build, TypeScript and 230 tests in 23 files passed, including production-WASM manifest round trips and recovery of an exact paid proof from a real devnet checkpoint. Regressions cover invalid inputs, wrong-network recovery, cancellation, persistence failures/timeouts and network/reader cleanup.npm pack --dry-run --jsonrepeated the checks successfully and inspected 97 packaged files, including both licences, bindings, WASM, provenance and notices. ADR governance and all 52 PR-checker cases passed.456dbb7streamed a native-produced 4,303,347,835-byte file to OPFS with matching BLAKE3, verified seeks and media ranges across 4 GiB, and rejected buffered output over a configured 64 MiB budget. This compressible fixture validates correctness, not representative throughput; the final pointer/export bundle was revalidated with small files.404fa34, as reported in feat(browser): export manifests and recover pointer payments ant-client#230: 854 library tests and 237 generated-WASM tests, formatting, and workspace and wasm32 clippy with-D warningspassed. Its real-Chromium suite against seven devnet nodes at26129a2611cac08f9f53a557f91fb8806b40a1ccand local Anvil passed all three tests, including lost-payment recovery, and the native CLI decoded a browser-created manifest.4d27758, as reported in feat(browser): export manifests and recover pointer payments ant-client#230: its real-Chromium suite against seven devnet nodes and local Anvil, including recovery of a deliberately lost pointer payment with one payment submitted, passed. #230 reports its library and generated-WASM tests at the rebased404fa34, not at4d27758.bab6beb, as reported in feat(browser): export manifests and recover pointer payments ant-client#230: 847 library tests and 234 generated-WASM tests, workspace and wasm32 clippy with-D warnings, and formatting passed.973ae93: 843 library tests and 229 generated-WASM tests, workspace and wasm32 clippy with-D warnings, and formatting passed. At8316468, ADR governance also passed.npm run verify:wasmpasses from84eace2, which bundles a build of ant-client main. Local browser tests use upstream's 1.5-second pacing for instant-Anvil operations; no mainnet, cross-browser or sustained-burst claim.New dependency
No new direct npm dependency. The bundled Rust dependency refresh updates
ant-protocol 3.1.0to5.0.1,saorsa-core 0.28.0to0.29.2,saorsa-transport 0.37.0to0.38.2andself_encryption 0.36.0to0.36.2, all from crates.io. Reviewers should acknowledge these bundled changes; exact sources and licence texts are recorded in generated notices and provenance.ADR
All remain Proposed for human engineering review; no Accepted ADR was changed.
Mitigation / rollback
Merging publishes 0.1.3: the release workflow re-verifies provenance and the checks on the merge commit, publishes, and tags it
v0.1.3. If that run fails, nothing is published, and a re-run resumes it. If rollback is needed, revert this PR and redeploy the matching service worker/WASM bundle; retain all submitted-payment evidence and paid proofs, and reconcile the original transaction rather than authorizing a replacement payment. Applications can retain buffered small-file downloads and explicit memory budgets while disabling new flows.🤖 Generated with Claude Code