Skip to content

feat: add large-file streaming, recoverable pointers and manifest exports - #7

Merged
mickvandijke merged 24 commits into
mainfrom
feat/large-file-streaming-20261015
Oct 27, 2026
Merged

mickvandijke merged 24 commits into
mainfrom
feat/large-file-streaming-20261015

Conversation

@mickvandijke

@mickvandijke mickvandijke commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

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() or PublicFileReader.pipeTo() into a disk-backed destination. Streaming reads and media no longer have a fixed 1 GB cap. download() and downloadAndSave() retain their buffered bytes/Blob contract and keep the former 1 GB limit as their default maxMemoryBytes; 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, and npm run verify:wasm passes. 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 .ant exports 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:

  • Pointer payments: the first checkpoint is the payment intent, persisted immediately before the wallet is called. PointerError.checkpoint is 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 through wait(), in the write and on resume from the same page. Checkpoint writes are serialized and coalesced, with paid bytes in base64.
  • Shutdown: every raw call leases the WASM handle. close() shuts the transport immediately, unless a paid pointer write is still storing, and frees the handle once no call borrows it.
  • Saves: destination failures reject with SAVE_FAILED, read failures with OPEN_FILE_FAILED. streamAndSave normalizes 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; downloadAndSave does the same. The streamAndSave capability 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:

  • Pointer writes pass { onPrepared, onPaid } as the core's trailing options object instead of two positional callbacks.
  • Recovery receipts: the core now needs each transaction named by quote hash. A wallet that returns one transactionHash paid every quote this journal recorded, so the SDK keys it by those quotes before calling Rust; a split receipt is passed through unchanged. PaymentProvider is unchanged.
  • The bundle also carries the core's other fixes: manifests refuse empty DataMaps rather than encoding them unreadably, as well as oversized root DataMaps and contradictory size hints. Unknown pointer options and a settlement refusal learned during onPrepared stop 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:

  • Checkpoint layout: the core's part of a pointer checkpoint (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.
  • Receipts: recoverPointerPayment now takes one transactionHash for 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.
  • Unaffected: the SDK already passed { onPrepared, onPaid }, and its onPrepared settles at once, so the core's new 30-second onPrepared limit 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 new onSubmission would only hand them back, and the journal also holds the wait() observers recovery uses. Manifest callers send only declared keys, which the core now enforces.
  • Docs: the README says a paid proof whose quotes are over a day old is refused as expired and cannot be stored without paying again.
Checkpoint re-encoder (ant-core at bab6beb, with its ant-protocol patch and Cargo.lock)
[package]
name = "reencode-pointer-checkpoint"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies]
ant-core = { path = "<ant-client checkout at bab6beb>/ant-core" }
base64 = "0.22"
rmp-serde = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[workspace]

[patch.crates-io]
ant-protocol = { git = "https://github.com/WithAutonomi/ant-protocol", rev = "718f60682bb3704b68fbba0bea34f2a0416fa876" }
//! Re-encode a pointer checkpoint from the ant-client 973ae93 layout (JSON byte
//! arrays) into the bab6beb layout (base64url record, base64url MessagePack
//! plan), exactly as `PointerPaymentCheckpoint::encode` writes it. Reads the old
//! checkpoint string on stdin and writes the new one to stdout.

use ant_core::data::client::batch::ChunkPaymentPlan;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use serde::{Deserialize, Serialize};
use std::io::Read;

/// The only checkpoint version either layout uses.
const POINTER_CHECKPOINT_VERSION: u64 = 1;

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct OldCheckpoint {
    version: u64,
    record: Vec<u8>,
    plan: ChunkPaymentPlan,
}

#[derive(Serialize)]
struct NewCheckpoint {
    version: u64,
    record: String,
    plan: String,
}

fn main() {
    let mut input = String::new();
    std::io::stdin().read_to_string(&mut input).expect("read stdin");
    let old: OldCheckpoint = serde_json::from_str(&input).expect("old checkpoint layout");
    assert_eq!(old.version, POINTER_CHECKPOINT_VERSION);
    let plan = rmp_serde::to_vec_named(&old.plan).expect("encode plan");
    let new = serde_json::to_string(&NewCheckpoint {
        version: POINTER_CHECKPOINT_VERSION,
        record: URL_SAFE_NO_PAD.encode(&old.record),
        plan: URL_SAFE_NO_PAD.encode(plan),
    })
    .expect("encode checkpoint");
    print!("{new}");
}

Built with ant-client's Cargo.lock (same rmp-serde 1.3.1, serde_json 1.0.150, base64 0.22.1). It writes what PointerPaymentCheckpoint::encode writes at bab6beb. Only prepared in test/fixtures/pointer-payment-devnet.json.gz was replaced.

Third companion follow-up (b69bbbb) rebuilds the bundled WASM from WithAutonomi/ant-client#230 at 4d27758 (1f2534b and 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:

  • Paid proof age: storePaidPointer no 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.
  • Unaffected: the core's onSubmission is 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's onSubmission, so neither applies. Every failure before the wallet now ends "payment was not submitted", which nothing in the SDK matches on. createManifest refuses 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 at 404fa34, 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 in b69bbbb:

  • Manifest SHA-256: main (ant-client #229) lets a manifest entry record the creator's SHA-256 of the plaintext, an optional field within format v1 that older readers ignore, and 404fa34 lets createManifest take one. ManifestEntry now carries it as optional hex sha256; the SDK dropped the field before, so manifests from the current CLI lost it when read here. ManifestEntryInput takes an optional sha256, which Rust validates.
  • Not checked (until 317a634, below): the SDK surfaces sha256 and 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.
  • Bundled crates: 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.

SDK follow-up (317a634) takes up two open items. The bundled WASM is unchanged (404fa34):

  • Manifest SHA-256 checked: download() and downloadAndSave() hold a manifest entry's bytes to its sha256 before returning or saving them, as the CLI does when extracting. A mismatch fails with DOWNLOAD_FAILED naming both hashes, and downloadAndSave() writes nothing; a malformed sha256 is refused with INVALID_SOURCE before 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() and createMediaSource() never hold the whole file and do not check it; the README says so.
  • Wallets wait for the journal: 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's onSubmission does 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 from void to void | Promise<void>, so existing submitted() {} 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 of 317a634, rebuilds the WASM from main 8b19c14 with the same rustc 1.99.0 and std copyright, and sets the version to 0.1.3:

  • Devnet run of 317a634 (audit): the first real-browser run since a4b6a53. It covers the manifest sha256 checks, 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.
  • What main adds to the bundle: 93f3bd2 checks unknown keys on any JavaScript object, which the SDK's own option objects and structuredClone copies 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) pins saorsa-transport to git revision 520dd3e. That crate is a normal dependency of the browser build, so the bundle carries it instead of the crates.io 0.38.1, and the licence notices record the git source. The pinned ant-node is only a dev-dependency and is not bundled. The JS bindings' exports and the SDK's public API are unchanged.
  • Devnet run of the release bundle (audit): the same scripts passed on their first run against nodes at the browser integration pin 26129a2, which main's browser tests still use.

Second main sync (56f4f6d) rebuilds the WASM from main ccd4106 (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-transport 0.38.2 instead of git revision 520dd3e, ant-protocol 5.0.1 and saorsa-core 0.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:

  • Plain media reader: 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.
  • No serial waits: the media service worker installs while the reader opens, and priming starts as soon as the reader is open, instead of each step waiting for the one before. An open failure still surfaces first.
  • Firefox keep-awake: while a media source is open the page messages the worker every 10 s. Firefox stops a worker that has had no new event for 30 s, even while it is answering a request.
  • Worker: it waits up to 120 s for a block instead of 45 s, since the core retries a missing record after 15 and 45 s. Its responses are marked nosniff and sandboxed, since their type comes from the file's metadata.
  • Measured: on mainnet, with try's 54.9 MB video and the same ccd4106 core 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 a v<version> tag pushed after the merge.

  • Push to main: the release workflow now also runs on pushes to main. If package.json holds a version with no v<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.
  • Unchanged: a push whose version is already tagged releases nothing and skips the checks, so merges that leave the version alone do nothing. Pushed tags still publish and must still match package.json and be on main. A manual run, on a branch or a tag, only checks and packs (fixed in 802db87). The tag the workflow creates with its own token starts no second run.
  • The README's "Releasing to npm" and CONTRIBUTING describe the new flow.

Review follow-up (3e6daec, 084e0b9, 6c2f7ef, 802db87) makes the four changes requested at bb38da8:

  • Manual runs never publish (802db87): a manual run on a v<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 into scripts/release-decision.sh, which test/release-decision.test.ts runs against a scratch repository, so npm run check covers it in CI and in the release itself.
  • Seeds are copied, not sliced (6c2f7ef): a Node Buffer's slice() shares its bytes, so zeroing the SDK's copy cleared the caller's seed after pointerAddress() or a pointer write. Both copy with new Uint8Array(seed).
  • A failed save leaves the picked file (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 the a4b6a53 follow-up and checked in the 65a8ce0 devnet run above.
  • The close-before-payment test waits for the write to resume (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 a4b6a53 predates 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

  • T0 — docs / tooling / CI / pure UX-output. Repo CI only.
  • T1 — client-only, no network-facing behavior change. CI + prod compat smoke.
  • T2 — node/client logic with behavioral surface, no protocol/format/economics change. Dev testnet + ADR.
  • T3 — protocol / storage format / payments / routing. T2 evidence + adversarial testing.

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 sha256 on download (ADR-0007).

Compatibility

  • Wire: no SDK-specific wire format is added. The WASM refresh adopts upstream ant-client and ant-protocol 5.0.0, including current pointer behavior. Earlier bundles of this PR were exercised against compatible browser-enabled nodes at 26129a2611cac08f9f53a557f91fb8806b40a1cc, and ant-client#230 exercised 404fa34 against the same nodes. The release bundle, built from main ccd4106 with every crate from crates.io, passed the real-browser run against the same nodes. No broad mixed-fleet compatibility claim.
  • Storage: existing network file records, DataMaps and manifest v1 remain unchanged; manifests may carry the optional sha256 ant-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 before a4b6a53 use an earlier unreleased layout and do not resume; checkpoints written before 12957cd hold the core's earlier unreleased checkpoint layout and do not resume either. Existing upload checkpoints are unchanged.
  • API: additive methods/types, capability reporting and POINTER_FAILED errors. streamAndSave and pipeTo return byte counts/hashes without a complete Blob; download/downloadAndSave retain their existing results and ordinary save fallback, and default to a 1 GB maxMemoryBytes (SDK_LIMITS.defaultDownloadMemoryBytes, the former cap); callers can pass another budget. Destination write failures in pipeTo/streamAndSave reject with SAVE_FAILED, read failures with OPEN_FILE_FAILED. The streamAndSave capability describes its default save-picker path. maxFileBytes remains the compatible upload-limit alias; media positions support exact JavaScript integers. Pointer writes require durable onCheckpoint persistence, preserve decimal counters and all five finality statuses, and resume without invoking a fresh payment. PointerError.checkpoint is undefined when no payment was requested. Manifest entries and createManifest entry inputs gain an optional hex sha256, which download/downloadAndSave check, failing with DOWNLOAD_FAILED on a mismatch or without WebCrypto. PaymentContext.submitted returns void | 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 new message listener.

Semver impact

  • breaking
  • feature
  • fix

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.

  • Review follow-up 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 of release.yml. Each new or changed test fails with the defect put back:
    • Release decision (7 tests): letting a manual run on a tag publish fails the manual-run test, and dropping the publish job's push condition fails the gate test.
    • Seeds: copying with slice() again fails the three Buffer tests (pointerAddress(), and a pointer write that is stored and one that fails).
    • Saves: the previous removal fails the three picked-file tests (failure before writing with an empty and a non-empty file, transfer failure, buffered download).
    • Close before payment: a deliberate wallet call once the paused checkpoint lands fails the changed test; the previous version of the test passed with it.
  • Release on merge 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 to latest; a v0.1.3 tag pushed before the merge is refused as not on main; a tag that does not match package.json is refused; a manual run checks without publishing; a -beta.1 version goes to beta; an unknown pre-release fails before publishing; and once v0.1.3 exists, 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.
  • Media first frame d06c176 (WASM unchanged, from ant-client main ccd4106): 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 the 8b19c14 WASM, whose browser and read source ccd4106 leaves unchanged; the side-by-side comparison with try.autonomi.com (637eba0, comparison) rebuilt every variant on the ccd4106 core, 12 rounds plus 8 traced, Chromium 151, with no failed sample or page error. The real-browser devnet run at 56f4f6d has not been repeated with this change.
  • Second main sync 56f4f6d (WASM from ant-client main ccd4106, version 0.1.3): npm run verify:wasm passed. 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 at 26129a2 and local Anvil, Chromium 151, and the CLI built from ccd4106 for native parity.
  • Main sync 84eace2 and 8c00e82 (WASM from ant-client main 8b19c14, version 0.1.3): npm run verify:wasm passed. SDK build, TypeScript and 258 tests in 23 files passed. All five example builds passed, as did npm 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 at 26129a2 and local Anvil, Chromium 151, and the CLI built from 8b19c14 for native parity.
  • Devnet run 65a8ce0 of 317a634 (WASM from 404fa34): real Chromium against seven WebRTC nodes at 26129a2 and local Anvil passed every check. download() and downloadAndSave() held public and embedded entries to their sha256: matching bytes were returned or saved; a wrong claim failed with DOWNLOAD_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 its manifest download agreed 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.
  • SDK follow-up 317a634 (WASM unchanged, from ant-client 404fa34): SDK build, TypeScript and 258 tests in 23 files passed. New tests: download() returns a manifest entry's bytes only when they match its sha256 (given in uppercase), fails with DOWNLOAD_FAILED on a mismatch or without WebCrypto, and refuses a malformed value with INVALID_SOURCE before 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 until submitted() 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.
  • Fourth companion follow-up b44ebfb (WASM from ant-client 404fa34): SDK build, TypeScript and 250 tests in 23 files passed. They include ant-client's golden fixture with a sha256, a sha256 carried through createManifest, decoding and its link, malformed values refused, and the re-encoded real devnet checkpoint still recovered and resumed; both sha256 pass-through checks fail without the src/manifest.ts change. All five example builds, npm pack --dry-run (97 files), ADR governance (8 ADRs) and all 52 PR-checker cases passed. npm run verify:wasm fails only because 404fa34 is not on ant-client main. The real-browser devnet run has not been repeated against this bundle.
  • Third companion follow-up b69bbbb (WASM from ant-client 4d27758): 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:wasm fails only because 4d27758 is not on ant-client main. The real-browser devnet run has not been repeated against this bundle.
  • Second companion follow-up 12957cd (WASM from ant-client bab6beb): 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:wasm fails only because bab6beb is not on ant-client main. The real-browser devnet run has not been repeated against this bundle.
  • Companion follow-up 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:wasm fails only because 973ae93 is not on ant-client main.
  • Review follow-up 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.
  • At 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.
  • All five example builds passed. npm pack --dry-run --json repeated 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.
  • Real Chromium 151 against seven isolated WebRTC nodes and local Anvil: public/private export and streaming save of 13,056-byte files, native decoding of browser-created manifests, pointer create/update/transfer, finality from all seven nodes, paid replay, and recovery after deliberately losing confirmation with exactly one payment submission. Demo export/save buttons passed using real OPFS writable files; native picker dialogs were not automated.
  • Earlier large-file implementation 456dbb7 streamed 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.
  • Companion ant-core at 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 warnings passed. Its real-Chromium suite against seven devnet nodes at 26129a2611cac08f9f53a557f91fb8806b40a1cc and local Anvil passed all three tests, including lost-payment recovery, and the native CLI decoded a browser-created manifest.
  • Companion ant-core at 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 rebased 404fa34, not at 4d27758.
  • Companion ant-core at 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.
  • Companion ant-core at 973ae93: 843 library tests and 229 generated-WASM tests, workspace and wasm32 clippy with -D warnings, and formatting passed. At 8316468, ADR governance also passed.
  • npm run verify:wasm passes from 84eace2, 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.0 to 5.0.1, saorsa-core 0.28.0 to 0.29.2, saorsa-transport 0.37.0 to 0.38.2 and self_encryption 0.36.0 to 0.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

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 dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 check stops at test/wasm.test.ts:16:46: Buffer<ArrayBufferLike> is not assignable to BufferSource in WebAssembly.compile. The detached base 29ab1758e1188fc445b82465949dcc655345935b fails 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

  1. Companion/provenance: WithAutonomi/ant-client#230 remains open at 8316468b7f5f813457f195bccb16a452426d5f35. I ran npm 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.
  2. 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.
  3. 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-only recover() or the retained wait() observer and storePaidPointer(); it never calls pay(). 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 === 0 guard 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 calls controller.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.

mickvandijke and others added 8 commits October 15, 2026 18:03
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>
@mickvandijke
mickvandijke marked this pull request as ready for review October 22, 2026 13:45
mickvandijke and others added 6 commits October 26, 2026 12:58
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 MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. Keep manual runs from publishing (.github/workflows/release.yml:50-69, 150). A workflow_dispatch run on a matching v<version> tag enters the tag branch and sets publish=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. Require github.event_name == 'push' in the publish gate, or only set publish=true for push events. Add a regression check for manual dispatch on both branch and tag refs. GitHub permits branch or tag refs for workflow dispatch.

  2. Copy caller-owned seed bytes before zeroizing (src/pointers.ts:81, 90, 150, 226). ownerSeedis aUint8Array, which includes Node Buffer. 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.

  3. 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 treats getFile().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.

  4. 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.

mickvandijke and others added 4 commits October 26, 2026 17:36
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>
@mickvandijke

Copy link
Copy Markdown
Member Author

Thanks. All four requested changes are in, one commit each, on top of bb38da8:

  1. Manual runs never publish (802db87): only a push decides to publish, and the publish job again also requires github.event_name == 'push'. The decision moved into scripts/release-decision.sh, and test/release-decision.test.ts runs it against a scratch repository. Manual runs on a branch and on a v0.1.3 tag both come out as check-only, and a further test checks the publish job's push gate. Letting a manual run on a tag publish again, or dropping the gate, fails those tests.
  2. Seed copies (6c2f7ef): both slice() calls are now new Uint8Array(seed). New tests pass Buffer seeds to pointerAddress() and to a pointer write that is stored and one that fails, and check the caller's bytes are unchanged while the copy handed to Rust is still zeroed. With slice() back, all three fail.
  3. Picked files (3e6daec): a failed save no longer removes the picked file on either path, since the SDK can't tell a file its picker created from an existing empty one. The pipe settlement tracking that served only that removal is gone, and the README says a failed save leaves the chosen file in place. The tests use pre-existing empty and non-empty handles on both save paths and check remove() is never called; the previous code fails them.
  4. Close-before-payment test (084e0b9): waiting for raw.freed was not enough, because closing rejects the write and frees the handle before the checkpoint is released. The test now lets everything chained to the checkpoint run before checking that no wallet call was made. A deliberate wallet call made once the checkpoint lands fails it; the previous version passed with that call.

npm run check (272 tests in 24 files), the example builds, npm pack --dry-run, verify:wasm, ADR governance and the PR-checker cases pass at 802db87. About the BufferSource error in test/wasm.test.ts: it doesn't occur here with the TypeScript version in package-lock.json. If you saw it after npm ci, could you share the TypeScript version?

🤖 Generated with Claude Code

@MaidSafe-Hermes MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 requires push.
  • 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.

@mickvandijke
mickvandijke merged commit 813aa18 into main Oct 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants