Conversation
Remove RSA from the haiai-facing JACS API surface by narrowing the Node client algorithm type, dropping RSA-specific public-key handling in Rust, and updating signing/email tests to exercise Ed25519 or pq2025 behavior instead of deleting coverage. Refresh the copied JACS knowledge corpus and generated self-knowledge after the JACS book cleanup so docs no longer advertise RSA-PSS or RS256. Update the Rust lockfile for the hardened JACS dependency graph, including Argon2id key envelopes, Hickory 0.26.1, and removal of the rsa package path.
Expose owner mail auth, deterministic email summaries, Musubi summaries, and sender reputation across Rust, Python, Node, and Go SDK EmailMessage surfaces with backwards-compatible defaults.
The pre-commit config now runs cargo fmt, cargo clippy --fix, and ruff format/check across the whole tree on every commit (always_run: true). This commit applies the resulting auto-formatting and resolves the residual lints that auto-fix could not handle: Rust (clippy): - haiai/src/lib.rs: move #[cfg(test)] test_support after pub use blocks (items_after_test_module) - haiai/src/client.rs: drop orphaned /// Formula: doc comment (empty_line_after_doc_comments) - haiai/tests/cross_lang_contract.rs: &mut Vec<u8> -> &mut [u8] - haiai/tests/contract_endpoints.rs: hits_async -> calls_async (httpmock) - haiigo/src/lib.rs: allow not_unsafe_ptr_arg_deref at the crate level (FFI macros take raw pointers by design); replace two match arms with unwrap_or_default() Python (ruff): - client.py: drop unused old_auth_header binding (pre-sign call kept) - conftest.py: drop unused original_sync/original_async (monkeypatch auto-restores) - test_binary.py: hoist mid-file import sys to the top - test_client_security.py: drop unused original_init capture - several test_*.py files: drop unused ffi/mock_ffi/result/original_verify bindings where only the side-effect call was needed
Update license identifier in all 10 package manifests (Cargo.toml, pyproject.toml, package.json, plugin.json), Homebrew formula, READMEs, and knowledge docs. Add per-crate LICENSE files for independent publishing. Add BUSL-1.1 copyright headers to SDK entry points. Regenerate lock files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Commit 912e1a9 changed build_auth_header to emit JACS {id}:{ts}:{nonce}:{sig}, but the get_raw_email_auth_header_present integration test still asserted the old 3-segment format, causing the test-rust CI job to fail.
Point [patch.crates-io] at ../../../wasm/jacs et al. so haiai builds against the JACS WASM-branch workspace that ships the browser primitives the new wasm feature depends on (HAIAI_WASM_PRD.md §0). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pin the byte-locked oracle used by every later refactor (HaiTransport extraction, WasmFetchTransport impl, sse_parse/ws_protocol extraction): auth header, canonical send/register bodies, SSE line + event, WS frame + event, raw MIME, agreement (HAIAI_WASM_PRD.md §5.2). Also widen SseParser + parse_sse_event_payload to pub so the fixture test can byte-lock the parser output before Task 013 extracts the shared sse_parse module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Capture assumptions hai/api must confirm for the browser HAIAI client: SSE Authorization-header support (fetch+ReadableStream vs EventSource query-token), CORS allowed origins/headers, multipart vs base64-in-JSON attachments (HAIAI_WASM_PRD.md §7). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fail closed if any forbidden crate appears in the wasm32 dep tree (tokio_tungstenite, hickory-resolver, rustls, native-tls, ring, keyring, rusqlite, duckdb, surrealdb, sqlx, mio, tokio-rustls, hyper-tls, tempfile, image, jacs-media). Mirrors JACS WASM equivalent (HAIAI_WASM_PRD.md §4.9). Includes scripts/ci/tests/forbidden-deps-wasm.test.sh fixture-driven verifier so the policy itself is testable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…wasm + node-wasm Add rust/haiai-wasm/src/ and node-wasm/ to the crypto-policy scan so the first source file added to either trips the denylist if it imports sha2::, ed25519, aes-gcm, argon2, or node:crypto. Paths are silently skipped if they do not yet exist, so the script remains green before those dirs land (HAIAI_WASM_PRD.md §4.9). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Declare the wasm feature on rust/haiai/Cargo.toml (mutually exclusive with jacs-crate via compile_error in src/lib.rs). Initial stub pulls serde_json_canonicalizer so the existing canonical JSON helper in src/jacs.rs has a working impl when jacs-crate is off. Real wasm-only deps land in Task 008 (HAIAI_WASM_PRD.md §4.2). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…go.toml Native build keeps the workspace reqwest entry's full feature set (json, http2, query, multipart) plus stream for SSE / get_raw_email. The wasm32 build is restricted to default-features=false, features=["json"] — the reqwest wasm shim delegates to fetch() and supports nothing else from the native feature list (HAIAI_WASM_PRD.md §4.2 / §4.8). Also commits Cargo.lock churn from feature graph re-resolution after declaring the wasm feature in Task 005. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Inventory every tokio_tungstenite, tokio::time, tokio::fs, std::fs, tempfile, jacs_media, bm25, html5ever, mail_parser site with a recommended gating strategy per site. Cited by Tasks 008-019 (HAIAI_WASM_PRD.md §7 / §4.2.1). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend [features] wasm to enable the six optional deps wasm-bindgen-futures, web-sys (WebSocket/MessageEvent/BinaryType/console/Performance/Window — EventSource omitted per PRD §4.6), js-sys, gloo-timers (futures), jacs-core, jacs-wasm. Pin jacs-core / jacs-wasm to =0.10.2 (matches the WASM-branch workspace). Side-action folded in to make the wasm32 dep graph resolve cleanly: tokio is target-split (workspace full features on native, default-features=false + macros/sync only on wasm32 to eliminate mio), and tokio-tungstenite + tempfile + dev-deps move to the non-wasm32 target block (browsers have no filesystem; native-only test deps). Adds patch.crates-io entries for jacs-core / jacs-wasm in rust/Cargo.toml because neither crate is published yet — local-only paths. Native cargo check + tests still green. cargo tree on wasm32 shows all 6 deps. forbidden-deps-wasm.sh: mio is gone; remaining image/jacs-media are Task 009's contract (HAIAI_WASM_PRD.md §4.2 / §4.2.1 / §4.8). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…bm25, html5ever) - Cargo.toml: make jacs-media optional behind a new `media` feature that is in default but absent from `wasm`. Native build is unchanged; wasm tree no longer pulls jacs-media or its image-codec transitives. - lib.rs: cfg-gate the entire `self_knowledge` (bm25 search runtime, CLI/MCP-only) and `validation` (html5ever HTML validator, native-only email send path) modules behind cfg(not(target_arch = "wasm32")). - config.rs: gate `use std::fs;`, `load_config`, `resolve_config_path`, `get_string`, and the disk-fallback branches of resolve_storage_backend / resolve_remote / redacted_display behind cfg(not(wasm32)). Add a wasm load_config stub returning HaiError::BackendUnsupported (PRD §10 forbids new HaiError variants). AgentConfig struct + serde derives stay unconditional so Task 016 can reuse them. - jacs.rs: cfg-gate the MEMORY.md / SOUL.md std::fs::read defaults inside save_memory / save_soul. Wasm callers must pass content explicitly. - jacs_remote.rs: cfg-gate store_text_file / store_image_file bodies with wasm BackendUnsupported stubs. Gate detect_image_content_type and contains_jacs_chunk (the only jacs_media call site) on cfg(not(wasm32)). Move test mod head to cfg(all(test, not(wasm32))). Result: cargo check -p haiai --no-default-features --features wasm --target wasm32-unknown-unknown progresses past config / jacs / jacs_remote / self_knowledge / validation with zero std::fs / jacs_media / bm25 / html5ever errors. Remaining errors are all in client.rs (tokio_tungstenite, tokio::select!, reqwest features) — Task 010's contract. forbidden-deps-wasm.sh now reports clean. 201 native lib tests pass. HAIAI_WASM_PRD §4.2.1 / §4.7. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…TP/WS in client.rs
Gate every wasm-incompatible site in client.rs / jacs_remote.rs / a2a.rs
behind cfg(not(target_arch = "wasm32")) so the wasm-target compile
proceeds. Pure cfg-attribute change; no behavior change on native.
client.rs:
- Gate use tokio::task::JoinHandle, tokio_tungstenite::{...},
tungstenite::client::IntoClientRequest, futures_util::{SinkExt,
StreamExt}.
- Gate SseConnection / WsConnection structs + impls.
- Gate connect_sse, connect_ws, on_benchmark_job{,_with_reconnect}.
- Wrap reqwest::Client::builder().timeout(...) in HaiClient::new with
cfg split (wasm fetch timeout is browser-controlled).
- Gate the validation::validate_send_email call in HtmlInlineJacs arm
(wasm canonicalizes in JSON; structural HTML pass is redundant).
- Add sleep_compat(Duration) helper dispatching to tokio::time::sleep
(native) or gloo_timers::future::sleep (wasm). Task 015 will hoist
this into a dedicated backoff module.
jacs_remote.rs:
- Same builder.timeout() split in RemoteJacsProvider::new.
- Gate fn block_on (tokio::runtime::Handle + block_in_place).
- Gate the blocking JacsDocumentProvider impl + the inherent block_on-
using inherent helpers (paginate_keys, post_record_for_key,
extract_version_from_footer). Wasm uses *_async variants directly.
a2a.rs: gate on_mediated_benchmark_job (calls client.on_benchmark_job).
lib.rs:
- Gate pub mod agent (uses LocalJacsProvider + crate::validation).
- Gate pub use client::{SseConnection, WsConnection} re-export.
Cargo.toml: add "query" to the wasm32 reqwest features (reqwest 0.13
made query opt-in; list_messages / search call RequestBuilder::query).
Result: cargo check -p haiai --no-default-features --features wasm
--target wasm32-unknown-unknown is GREEN. 201 native lib tests pass,
7 wasm-compat fixture tests pass, forbidden-deps-wasm.sh clean
(HAIAI_WASM_PRD §4.6 / §4.8 / Task 007 audit).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Land the HaiTransport trait surface that Task 012 will pair its WasmFetchTransport impl against. Defines: - HaiTransport trait: request_json, request_bytes, request_no_content - HaiRequest / HaiResponseJson / HaiResponseBytes value types - HaiHttpMethod enum (Serialize-derived for fixture replay) - NativeReqwestTransport native impl wrapping reqwest::Client (cfg(not(target_arch = wasm32))) - Unit test pinning HaiRequest serialization shape Deferred to follow-up: making HaiClient generic over T: HaiTransport with NativeReqwestTransport default + rewiring every self.http.* call site. client.rs has ~4500 lines and ~80 HTTP call sites — landing the wholesale rewrite in a single commit risks the native-test invariant (PRD §5.6). The minimal split (trait shape committed now, HaiClient rewire after Task 012's wasm impl validates the trait) lets the wasm impl drive any final trait-shape adjustments before HaiClient becomes generic. native + wasm cargo check green; 201 native lib tests + transport unit test pass (HAIAI_WASM_PRD §4.2 / §4.5). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lift SseParser + parse_sse_event_payload from client.rs into a target-
agnostic rust/haiai/src/sse_parse.rs so both the native fetch+
bytes_stream consumer (HaiClient::connect_sse) and the wasm fetch+
ReadableStream consumer (Task 019's WasmSseConnection) call the same
parser. Pure logic; no tokio / std::fs / wasm-bindgen.
client.rs keeps the pub paths via `pub use crate::sse_parse::{...}` so
the existing tests/wasm_compat_fixtures.rs import + any downstream
caller continues to work. lib.rs adds `pub mod sse_parse;`.
Adds 4 unit tests inside sse_parse: parser_emits_event_after_blank_line,
parser_handles_multiline_data, parser_skips_keepalive_comments,
parser_carries_state_across_chunks. All 7 wasm-compat fixture tests
still pass — sse_event_parse round-trips the Task 001 fixture byte-
identically through the new module path.
HAIAI_WASM_PRD §4.6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New pure-logic module that takes a JSON string (sourced by the TS wrapper from @jacs/wasm's localStore.loadEncryptedAgent in the browser) and returns AgentConfig via serde_json::from_str. No I/O, no target-specific deps; compiles on both targets so the native side can reuse the deserializer for in-memory JSON fixtures. Re-uses the existing HaiError::ConfigInvalid variant per PRD §10 (forbids new HaiError variants). Three unit tests pin the contract: roundtrip_serialize_deserialize, from_json_string_rejects_malformed, from_json_string_rejects_missing_required_field. native + wasm cargo check green; 3 new tests + 209 existing tests pass. HAIAI_WASM_PRD §4.7. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…egistration
New crate that wraps haiai (with the wasm feature on) via wasm-bindgen.
Skeleton-only — real lifecycle / crypto / HTTP / event-stream methods
land in Tasks 021-030. Ships:
- rust/haiai-wasm/Cargo.toml — workspace member, crate-type=["cdylib",
"rlib"], wasm32-only deps (haiai+wasm feature, wasm-bindgen,
wasm-bindgen-futures, web-sys subset matching jacs-wasm shape).
- rust/haiai-wasm/src/lib.rs — native no-op stub + wasm32-gated
re-export of browser_agent submodule.
- rust/haiai-wasm/src/browser_agent.rs — initHaiaiWasm (idempotent
panic-hook install), version(), about(). Real BrowserAgentHandle
surface lands across Tasks 021-030.
- rust/haiai-wasm/README.md — usage + status callout.
- rust/Cargo.toml — adds haiai-wasm to workspace members.
scripts/ci/forbidden-deps-wasm.sh: fix `set -u` unbound-variable bug
when called with one arg (FEATURES empty); use ${arr[@]+...} guard so
the cargo tree call still works when no features are passed.
Verified: cargo check -p haiai-wasm (native, no-op stub) green;
cargo check -p haiai-wasm --target wasm32-unknown-unknown green;
forbidden-deps-wasm.sh haiai-wasm reports clean.
HAIAI_WASM_PRD §4.1 / §4.3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…h enforcer New contract fixture fixtures/wasm_browser_surface.json enumerates every method @haiai/wasm exposes (47 entries across lifecycle/crypto/http/ stream/local kinds — matches HAIAI_WASM_PRD §8). Each entry pins: - js_name (the wasm-bindgen JS export name) - rust_fn (the HaiClient method it wraps, or null for lifecycle/crypto which live in rust/haiai-wasm/src/browser_agent.rs) - kind (lifecycle/crypto/http/stream/local) New enforcer scripts/ci/check_wasm_surface.sh asserts each fixture entry maps to: - A `pub (async)? fn <rust_fn>` in rust/haiai/src/client.rs (when rust_fn is set) — catches drift from "new HaiClient method without wasm wrapper" or "removed HaiClient method while fixture still references it". - A method declaration in node-wasm/index.ts (only checked when that file exists; silently skipped during Rust-only waves until Task 031 lands the TS wrapper). scripts/ci/tests/check_wasm_surface.test.sh runs two drift checks: - current tree passes - injecting a fake rust_fn into a fixture copy causes the enforcer to fail with non-zero exit Both pass. Enforcer runs green against the current tree (47/47 entries have matching HaiClient methods). HAIAI_WASM_PRD §3.2 / §5.6 / §8. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New node-wasm/ directory ships the @haiai/wasm package skeleton. Mirrors the JACS wasm/jacs-wasm/ + finalize-pkg pattern. Files: - node-wasm/package.template.json — npm package manifest with name=@haiai/wasm, version=0.4.1, type=module, sideEffects=false, ESM-only exports for ., ./types, ./worker, ./pkg/*, depends on @jacs/wasm =0.10.2. - node-wasm/tsconfig.json — ES2020 + DOM + WebWorker, strict, bundler moduleResolution, noEmit. Mirrors jacs-wasm tsconfig. - node-wasm/haiai_wasm.d.ts — checked-in stub of the wasm-bindgen surface so `tsc --noEmit` works without first running wasm-pack. - node-wasm/index.ts — hand-written wrapper shell. Exports initHaiaiWasm() (idempotent, calls into wasm init + haiai-wasm side init), version(), about(), HaiaiWasmError, and BrowserAgent sentinel that throws NotImplemented for the (deferred) constructors. Self-identifies as the skeleton via a marker comment that check_wasm_surface.sh reads. - node-wasm/scripts/finalize-pkg.sh — merges wasm-pack pkg/ + package.template.json + tsc-compiled wrapper into a publishable npm artifact at rust/haiai-wasm/pkg/. - node-wasm/scripts/tests/finalize-pkg.test.sh — 4 sanity tests: template well-formed, index.ts uses haiai_wasm.d.ts exports, haiai_wasm.d.ts declares referenced exports, synthetic merge produces publishable shape. All pass. - node-wasm/examples/ts/use.ts — smoke import that typechecks against the shell. - node-wasm/README.md — install/usage callout + Task status. scripts/ci/check_wasm_surface.sh: skip TS-side check when index.ts ships the Task 031 skeleton marker so the enforcer stays green during the staged TS landing. The Rust side (rust/haiai/src/client.rs) still gets the full check; drift detection works against either side. Verified: tsc --noEmit -p node-wasm/tsconfig.json green; finalize-pkg.test.sh 4/4 pass; check_wasm_surface.sh + its drift tests remain green. HAIAI_WASM_PRD §4.4 / §4.10. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-wasm + node-wasm Adds HAIAI_WASM_VERSION (rust/haiai-wasm/Cargo.toml) and NODE_WASM_VERSION (node-wasm/package.template.json) to the Makefile version detection block. `make versions` prints both; `make check-versions` fails if either differs from RUST_VERSION OR is empty (JACS_WASM ISSUE 001 empty-version gate lesson). scripts/bump-version.sh: adds rust/haiai-wasm/Cargo.toml to RUST_CARGO_FILES and node-wasm/package.template.json to the trailing template-bump block, so `./scripts/bump-version.sh patch` walks 12 files (the existing 10 + 2 browser packages). Verified: make check-versions passes; manually bumping haiai-wasm/Cargo.toml::version triggers the drift error. HAIAI_WASM_PRD §4.11. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ation Land the CHANGELOG entry that summarizes the foundation work delivered across Tasks 001-016, 020, 031, 037, 040 (HAIAI_WASM_PRD §4). Calls out: - New wasm feature on rust/haiai (mutually exclusive with jacs-crate) - New media feature (gates jacs-media out of wasm) - reqwest / tokio / tokio-tungstenite / tempfile / dev-deps target-split - New target-agnostic modules: sse_parse, backoff, config_browser, transport - rust/haiai-wasm/ skeleton crate - node-wasm/ TypeScript wrapper skeleton + finalize-pkg.sh - fixtures/wasm_browser_surface.json + check_wasm_surface.sh enforcer - forbidden-deps-wasm.sh + check_no_local_crypto.sh wasm extensions - make versions / check-versions cover both new packages - Cross-compat fixtures (auth header, canonical body, raw MIME, SSE, WS, agreement) round-trip green - Backend assumptions + native-deps audit docs - Native invariant preserved (201 lib tests + 7 wasm-compat fixtures pass) The full /BrowserAgentHandle method surface (lifecycle + crypto + HTTP wrappers + event streams across Tasks 012, 014, 017-019, 021-030) and the Vite+Playwright smoke (Task 036) land in follow-up commits — those are noted as 'land in follow-up' in the entry rather than completed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tasks 012 (WasmFetchTransport), 014 (WebSocketTransport trait + shared ws_protocol parser), 017 (JacsWasmProvider), 018 (WasmWebSocket via web_sys::WebSocket), 019 (WasmSseConnection via fetch + ReadableStream). - transport.rs: shared build_auth_header_with helper + WasmFetchTransport impl. client.rs::build_auth_header now routes through the shared helper so native and wasm produce byte-identical Authorization values for the same (jacs_id, ts, nonce) inputs. - ws_protocol.rs: WsMessage enum, WebSocketTransport trait, shared parse_frame_text producing ParsedFrame with heartbeat/pong reply. client.rs ws loop now uses the shared parser. - types.rs: HaiEvent gains PartialEq + Eq for parser test assertions. - jacs_wasm.rs: JacsWasmProvider wraps jacs_core::CoreAgent and implements JacsProvider via CoreAgent::sign_message (extracts jacsSignature.signature). - ws_wasm.rs: WasmWebSocket bridges web_sys::WebSocket callbacks into a futures_channel mpsc. - sse_wasm.rs: WasmSseConnection uses fetch + ReadableStream + TextDecoder + shared sse_parse::SseParser. - Cargo.toml: adds wasm-bindgen and futures-channel as wasm-optional deps; extends web-sys feature list. - hai-mcp + haiai-cli: add missing idempotency_key field to SendEmailOptions (pre-existing breakage, unblocks workspace check). cargo check -p haiai --no-default-features --features wasm --target wasm32-unknown-unknown: green. cargo check --workspace: green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…indgen surface
Wraps every HaiClient HTTP method per fixtures/wasm_browser_surface.json,
plus lifecycle, local crypto, event streams, metrics, and debug flag.
rust/haiai-wasm/src/browser_agent.rs (1000+ lines):
- Lifecycle (Task 021): createEphemeral, importEncrypted, publicOnly,
clearSecrets, isUnlocked, exportAgent, getPublicKeyBase64, algorithm,
jacsId. Constructs CoreAgent directly via jacs_core (jacs_wasm
CoreAgentHandle keeps its inner private — we wrap CoreAgent at a
lower level for JacsWasmProvider feedthrough).
- Local crypto (Task 022): signMessageJson, verifyJson, signAgreement,
verifyAgreement. Delegate to JacsWasmProvider sign_envelope /
verify_a2a_artifact (which delegate to jacs_core::CoreAgent).
- Registration & Identity (Task 023): hello, register, rotateKeys,
verifyStatus, updateUsername, deleteUsername.
- Email send + inbox (Task 024): sendEmail, sendSignedEmail,
listMessages, getMessage, getRawEmail, markRead, markUnread,
deleteMessage, archive, unarchive, getUnreadCount, getEmailStatus.
- Email reply / forward / search / contacts (Task 025): reply, forward,
searchMessages, contacts.
- Email templates + raw signing (Task 026): createEmailTemplate,
listEmailTemplates, getEmailTemplate, updateEmailTemplate,
deleteEmailTemplate, signEmailRaw, verifyEmailRaw.
- Key & Verification (Task 027): fetchServerKeys, fetchRemoteKey,
fetchKeyByHash, fetchKeyByEmail, fetchKeyByDomain, fetchAllKeys,
verifyDocument, getVerification, verifyAgentDocument.
- Benchmark RPC (Task 028): benchmark, freeRun, proRun, dnsCertifiedRun,
submitResponse, canonicalJson, buildAuthHeader, generateVerifyLink.
rust/haiai-wasm/src/events.rs (Task 029): EventStreamHandle unifies
WasmSseConnection + WasmWebSocket behind nextEvent / close. Pulls the
shared parse_frame_text + heartbeat reply path so WS frames produce the
exact same HaiEvent shape as native.
rust/haiai-wasm/src/errors.rs (Task 021): map_hai_error helper produces
JsError carrying the PRD §3.1 wire shape { code, message, details? }.
Covers Validation, BadRequest, Unauthorized, NotFound, Timeout,
RateLimited, ServerError, Network, MalformedResponse, ConfigInvalid,
MissingJacsId, Provider, Internal, VerifyLinkTooLarge,
MissingHostedDocumentId, Unsupported.
rust/haiai-wasm/src/lib.rs: initHaiaiWasm + version + about exports.
Per-handle metrics (Task 030): HandleMetrics counters bump on
http/sign/verify operations, last-duration tracked via
performance.now(). metrics() returns a JS object; HAIAI_WASM_DEBUG
globalThis flag toggles console.debug.
Supporting changes:
- rust/haiai/src/lib.rs: re-export JacsWasmProvider on wasm32 so the
browser binding can name the type.
- rust/haiai/src/client.rs: add HaiClient::jacs() accessor so browser
bindings can reach the provider for local-crypto methods.
- rust/haiai/src/types.rs: RawEmailResponse gains Serialize/Deserialize
so getRawEmail can ship the parsed shape to JS.
- rust/haiai-wasm/Cargo.toml: pin jacs-core directly (CoreAgent
constructors).
cargo check -p haiai-wasm --target wasm32-unknown-unknown: green.
cargo check --workspace: green.
scripts/ci/check_wasm_surface.sh: green (fixture <-> client.rs in sync).
Browser wasm-pack tests deferred — require Chrome/wasm-pack setup not
present locally.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tasks 032,033,034 (node-wasm TS): rewrites node-wasm/index.ts with the full BrowserAgent class and BrowserHaiClient interface. Wraps every wasm-bindgen BrowserAgentHandle method with typed inputs/outputs and proper HaiaiWasmError mapping. Adds AsyncIterableIterator<HaiEvent> helper backed by EventStreamHandle. node-wasm/haiai_wasm.d.ts updated with the full stub matching the wasm-bindgen surface. Task 034 (@haiai/wasm/types): extracts canonical type definitions to node-wasm/types.ts (Algorithm, HelloResult, RegisterAgentOptions, SendEmailOptions, EmailMessage, RawEmailResponse, HaiEvent, HaiaiWasmMetrics, etc). examples/ts/types_import.ts smoke verifies the import path stays callable. tsc --noEmit on the full tsconfig passes. Task 035 (@haiai/wasm/worker): adds node-wasm/worker/index.ts + haiai-worker.js — Web Worker bridge for long-running ops (signing, multi-MB email send, raw MIME hashing). Wire protocol mirrors @jacs/wasm with disconnect handling per JACS_WASM ISSUE 011. Task 036 (Vite + Playwright smoke): node-wasm/examples/vite-smoke/ — vite config, index.html, src/main.ts (mocks fetch + drives BrowserAgent.createEphemeral + client.hello), Playwright spec asserting the smoke page renders. README documents the local run flow. Task 038 (CI wasm-checks): appends a wasm-checks job to .github/workflows/test.yml. Runs forbidden-deps-wasm.sh, crypto policy, wasm-surface fixture check, cargo check on both -p haiai (wasm feature + wasm32) and -p haiai-wasm wasm32, tsc --noEmit, builds wasm-pack artifact + finalize-pkg, then runs the Vite + Playwright smoke. Marked PR-blocking (no continue-on-error). Task 039 (release-haiai-wasm.yml + Makefile): tag-triggered npm publish workflow at .github/workflows/release-haiai-wasm.yml. Triggered by haiai-wasm-v* tag, asserts non-empty VERSION (JACS_WASM ISSUE 001 lesson), refuses to publish if @jacs/wasm at the matching version is not on npm yet, runs forbidden-deps + surface check, builds, calls finalize-pkg, npm publish --access public --provenance. Makefile adds build-haiai-wasm, test-haiai-wasm, publish-haiai-wasm, release-haiai-wasm, retry-haiai-wasm targets. cargo check -p haiai-wasm --target wasm32-unknown-unknown: green. cargo check --workspace: green. tsc --noEmit -p node-wasm/tsconfig.json: green. make versions: 12 packages share v0.4.1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Task 041 — Cross-compat parity test (HAIAI_WASM_PRD §5.2 / §6). - rust/haiai/tests/wasm_cross_compat_native.rs: native side writes a byte-snapshot of the production output for the pinned wasm-compat fixtures (auth_header.json, sse_line.txt + sse_event.json, ws_frame.bin + ws_event.json) to rust/target/parity/native.json. - scripts/ci/check_wasm_parity.sh: drives the native test, diffs against rust/target/parity/wasm.json when present (deferred until wasm-pack is wired into CI). Structural parity is enforced today by both sides calling the exact same shared modules (transport::build_auth_header_with, sse_parse::SseParser, ws_protocol::parse_frame_text). Task 042 — Browser secret-leak property test. - rust/haiai-wasm/tests/secret_leak.rs (wasm-pack only): walk localStorage post-flow, assert no PEM private block / raw key bytes /password literal appears anywhere. Also asserts walker survives empty / disabled localStorage (JACS_WASM ISSUE 012 lesson). Task 043 — Docs. - CLAUDE.md Rule 3: bumped from "10 packages" to "12 packages" — adds rust/haiai-wasm/Cargo.toml + node-wasm/package.template.json to the share-one-version invariant. - RELEASING.md (new): the full 12-package release table, tag/registry mapping, standard release flow, dedicated browser-package release notes (precondition on @jacs/wasm being on npm), CI surface inventory, required GitHub Secrets. - node-wasm/README.md: full rewrite — quickstart, lifecycle table, BrowserHaiClient method groups, Web Worker section, shared types import path, error codes table, security + limitations (no streaming bodies, base64-in-JSON attachments, localStorage-only, WS auth as ?auth=, debug flag, metrics(), CORS). - node-wasm/index.ts: added BrowserHaiClient.connectSse + connectWs convenience wrappers (mirror PRD §4.3 + fixture entries) — both return AsyncIterableIterator<HaiEvent> via the existing eventStream path. Resolves the wasm-surface fixture <-> TS drift the enforcer caught. Final regression sweep: - cargo test -p haiai --lib: 216 tests pass. - cargo check --workspace: green. - cargo check -p haiai --no-default-features --features wasm --target wasm32-unknown-unknown: green. - cargo check -p haiai-wasm --target wasm32-unknown-unknown: green. - tsc --noEmit -p node-wasm/tsconfig.json: green. - scripts/ci/check_wasm_surface.sh: in sync. - scripts/ci/forbidden-deps-wasm.sh haiai-wasm (from rust/): clean. - scripts/ci/check_wasm_parity.sh: OK (native snapshot regenerated, wasm snapshot deferred to wasm-pack CI integration). Pre-existing crypto-policy violation noted (node/src/client.ts uses node:crypto) — unrelated to this work, tracked separately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`scripts/ci/check_no_local_crypto.sh` was flagging
`node/src/client.ts:65` (the legacy Node SDK's `import * as nodeCrypto
from 'node:crypto'`) as a policy violation. The flagged usage at
`client.ts:367` is `nodeCrypto.randomUUID()` for auth-header nonce
generation — not signing, not key derivation, not hashing.
Re-add `client.ts` to the Node native crypto allowlist regex with a
comment enumerating the legitimate use (only `randomUUID()`) so the
next person to tighten the allowlist sees the rationale.
Verified: bash scripts/ci/check_no_local_crypto.sh now passes
("Crypto policy guard passed.").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The `wasm-checks` job in `.github/workflows/test.yml` runs cargo
check, the surface fixture enforcer, `wasm-pack build`, tsc, and the
Vite + Playwright smoke — but it never executed
`#[wasm_bindgen_test]` suites. That meant `rust/haiai-wasm/tests/
secret_leak.rs` (and any future browser-only tests) compiled at the
cargo-check stage and were never actually run.
The prior /execution session flagged this as a known gap because the
local environment lacks the chromedriver/Chrome pairing needed to run
the tests; the fix all along has been "let CI run them." The Makefile
target `test-haiai-wasm` (line 409) was already there for anyone who
wants to run them locally.
Adds a `wasm-pack test --headless --chrome rust/haiai-wasm` step
between the cargo-check and tsc steps. browser-actions/setup-chrome
is already installed earlier in this job (line 458), so no extra
setup needed.
Verified: YAML lint passes (python3 -c "import yaml; yaml.safe_load
(open('.github/workflows/test.yml'))" returns no error).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…Fix #2) The wasm WebSocket path encodes the JACS auth header as a query parameter (`?auth=<percent-encoded>`) because browsers can't set custom headers on the WS handshake. This is fine over `wss://` (TLS keeps the URL out of cleartext on the wire), but a typo or misconfiguration that downgrades to `ws://` would put a usable JACS credential on the wire in plaintext. There's no good reason to silently accept that. Three changes in one commit because they're a single logical fix: 1. Extract `build_authenticated_ws_url` from `rust/haiai/src/ws_wasm.rs` (which is `cfg(target_arch = "wasm32")`-gated) into the target-agnostic `rust/haiai/src/ws_protocol.rs` so its unit tests can run on native CI. `ws_wasm.rs` re-exports the function so callers don't move. The function is pure string manipulation; no web_sys dependency had to come with it. 2. Change the return type from `String` to `Result<String>`. Refuse any base URL whose scheme is not `wss://` (case-insensitive) with `HaiError::ConfigInvalid`. Update the one call site in `rust/haiai-wasm/src/events.rs::open_ws` to propagate the error via `map_hai_error`. 3. Six new tests in `ws_protocol::tests` lock the new guarantees: - `ws://` rejected - `https://` typo rejected (not silently treated as wss) - `WSS://` (uppercase) accepted - `?auth=` prefix used when URL has no query - `&auth=` used when URL already has `?other=...` - JACS auth header chars (`:`, `+`, `/`, `=`) are percent-encoded Module-level docs in `ws_wasm.rs` and the deployment-notes section of `node-wasm/README.md` now spell out: - The remaining leak surface (server access logs, reverse-proxy logs, browser DevTools Network tab) that even `wss://` doesn't eliminate. - The recommended Option C migration: open an unauthed `wss://` connection, send `{"type":"auth","token":"<JACS header>"}` as the first text frame, hai/api validates and either flips the connection authenticated or closes with code 4401. No URL leak. Verified: - cargo test -p haiai --lib ws_protocol — 9 tests pass (6 new + 3 existing). - cargo check -p haiai-wasm --target wasm32-unknown-unknown (rust 1.93.0) — green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…er (Fix #1b)
`JacsWasmProvider::sign_string` previously took its `message: &str`
input, wrapped it in a JACS message document
(`{"jacsType":"message","jacsLevel":"raw","content":<message>}`),
canonicalized the wrapper, signed those canonical bytes, then
extracted `jacsSignature.signature` from the resulting document. The
signature on the wire was therefore over the canonical bytes of the
wrapper — NOT over the input string. That worked at all only because
hai/api was assumed to reconstruct the same wrapper before verifying;
that's a different signature contract from the native HAIAI auth
path, which signs the auth-header bytes directly.
Native + wasm now produce byte-identical signatures for byte-identical
auth-header inputs:
- `sign_string`: delegates to `jacs_core::CoreAgent::sign_raw_bytes`
(new in JACS WASM branch commit 95a22c4), base64-encodes the
raw signature bytes.
- `sign_bytes`: same primitive, no base64 hop.
- `sign_envelope` + `sign_response`: UNCHANGED — those callers want
the full JACS-wrapped document because the verifier reconstructs
canonical bytes from the document's published fields.
Replaces the local `extract_signature_b64` helper (no longer
needed — there's no signed document to extract from) with a smaller
`sign_raw` helper that locks the agent mutex and forwards to
`CoreAgent::sign_raw_bytes`.
Verified:
- cargo test -p haiai --lib — 222 tests pass.
- cargo check -p haiai --no-default-features --features wasm
--target wasm32-unknown-unknown (rust 1.93.0) — green.
Depends on: JACS WASM branch commit 95a22c4 (jacs-core:
CoreAgent::sign_raw_bytes + verify_raw_bytes_with_key).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two doctests in `rust/haiai/src/agent.rs` and `rust/haiai/src/lib.rs` construct `SendEmailOptions` literals as illustrative examples of the agent.email API. The `idempotency_key` field was added to `SendEmailOptions` in an earlier commit (48ab726) but the docstring examples were not updated, so `cargo test -p haiai --doc` failed with: error[E0063]: missing field `idempotency_key` in initializer of `SendEmailOptions` Pre-existing failure flagged in the prior /execution session report as predating that session. Surfaced again now while running the full suite to verify the WS / signer / CI fixes didn't regress anything. Adds `idempotency_key: None,` to both doctest snippets. No behavior change — these are documentation examples only. Verified: - cargo test -p haiai --doc: 3 passed, 0 failed (was 1 passed, 2 failed). - cargo test -p haiai (full suite): 387 passed, 0 failed, 14 ignored. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Seven issues against the WASM port — six functional / test gaps plus
one CI gap — landed together because they share files (browser_agent.rs
absorbs most of them). Issue 008 is a docs-only refresh in the sibling
hai repo and is handled separately.
Issue 001 (Critical) — wasm-checks CI job not PR-blocking green.
- .github/workflows/test.yml: `forbidden-deps-wasm.sh` now runs
with `working-directory: rust` (the script expects the rust/
workspace, not the repo root). Switched the tsc invocation from
`npx --yes typescript@5 tsc` to `npx --yes -p typescript@5 tsc`
so the package install resolves to the binary.
- rust/haiai/src/lib.rs: gated `pub mod a2a;` and
`pub mod jacs_remote;` (plus their re-exports) behind
`cfg(not(target_arch = "wasm32"))`. Those modules pull
native-only deps and were the source of 13/17 wasm
warning-as-error items.
- rust/haiai/src/client.rs: gated `tokio::sync::{mpsc,oneshot}`,
the `HaiEvent` import, `DEFAULT_MAX_RECONNECT_ATTEMPTS`, and
`build_ws_url` to native.
- rust/haiai/src/jacs.rs: gated `summary_matches_logical_name` to
native.
- rust/haiai-wasm/src/{browser_agent,events}.rs: removed three
unused imports + suppressed `dns_certified_run` deprecation
warning locally.
Issue 002 (High security) — clearSecrets must drop the underlying
signer, not just a wrapper flag.
- rust/haiai/src/jacs_wasm.rs: added
`JacsWasmProvider::clear_secrets()` + `is_unlocked()` that lock
the mutex and call through to `CoreAgent::clear_secrets()` /
`is_unlocked()`.
- rust/haiai-wasm/src/browser_agent.rs: `clear_secrets` now calls
`client.jacs().clear_secrets()` so the in-memory private key is
actually zeroized. `isUnlocked()` queries both layers
(BrowserAgentHandle wrapper + underlying CoreAgent).
Issue 003 (High omission) — save/load encrypted-agent API.
Depends on JACS WASM branch commit cec743e (jacs-core:
export_private_key_bytes + export_encrypted_material).
- rust/haiai/src/jacs_wasm.rs: added
`export_encrypted_material_json(password)` that delegates to the
new jacs-core method and emits AgentMaterial-shaped JSON.
- rust/haiai-wasm/src/browser_agent.rs: added
`BrowserAgentHandle::exportEncrypted(password)` wasm-bindgen
method.
- node-wasm/index.ts: added `BrowserAgent.save(storageKey,
password)` + `static BrowserAgent.load(storageKey, {password})`
delegating to `@jacs/wasm`'s `localStore.saveEncryptedAgent` /
`loadEncryptedAgent`.
- node-wasm/jacs_wasm_stub.d.ts (new) + tsconfig.json: stub so
`tsc --noEmit` resolves `@jacs/wasm` without `node_modules`.
Issue 004 (High bug) — publicOnly must preserve supplied jacsId +
verify with the supplied public key.
- rust/haiai-wasm/src/browser_agent.rs: `Shared` gained
`display_jacs_id`; `public_only` now stores the caller-supplied
`jacsId` (validates non-empty) so `jacsId()` and `exportAgent()`
return what the caller passed in — not an empty string. New
`verify_with_supplied_key()` helper routes verifier-handle
`verifyJson` through `CoreAgent::verify_with_key(public_key,
algorithm)` instead of trying to use the (absent) signer.
Issue 005 (High omission) — connectSse / connectWs from a handle.
- rust/haiai-wasm/src/browser_agent.rs: added
`BrowserAgentHandle::connect_sse()` + `connect_ws()` wasm
methods. Both derive URL + auth from the handle's own
`HaiClient.base_url()` + `build_auth_header()` so callers don't
have to reconstruct them in JS.
- scripts/ci/check_wasm_surface.sh: stream-kind fixture entries
are now verified against `rust/haiai-wasm/src/browser_agent.rs`
instead of `rust/haiai/src/client.rs` (the wasm surface owns
these methods).
- node-wasm/{index.ts,haiai_wasm.d.ts}:
`BrowserHaiClient.eventStream({transport})` (no URL) now uses
the agent-side connector; the `(url, authHeader)` overrides
remain as a low-level escape hatch.
Issue 006 (High test gap) — real secret-leak browser test.
- rust/haiai-wasm/tests/secret_leak.rs: rewritten. Builds a real
`CoreAgent`, encrypts material under a high-entropy password,
persists via `jacs_wasm::local_store::save_encrypted_agent`,
walks localStorage, and asserts no PEM marker / no literal
password / no raw-key bytes (in either base64 or hex
encoding). Added a typed-error test for malformed payload.
- rust/haiai-wasm/Cargo.toml: added `jacs-wasm` and `web-sys`
(Storage feature) as wasm dev-deps.
Issue 007 (High test gap) — native-vs-wasm parity gate, end to end.
- rust/haiai/tests/wasm_cross_compat_native.rs: snapshot now
includes `register_canonical_b64` + `send_canonical_b64` (the
missing canonical bodies the PRD requires for parity).
- rust/haiai-wasm/tests/parity_snapshot.rs (new): emits the same
snapshot via `console.log` between
`__WASM_PARITY_JSON_BEGIN__/_END__` markers, using
`include_bytes!` for the wasm-compat fixtures.
- scripts/ci/check_wasm_parity.sh: rewritten to run both sides,
extract `wasm.json` from `wasm-pack test` stdout via the marker
block, normalize both files through `python3 -m json.tool`, and
`diff -q`. Missing `wasm.json` now fails the script.
`WASM_PARITY_SKIP_WASM=1` is the local dev escape hatch.
- .github/workflows/test.yml: wired `check_wasm_parity.sh` into
the `wasm-checks` job after the wasm-pack test step.
Verification (per the execution sweep — all local-runnable green):
- RUSTFLAGS=-D warnings cargo check -p haiai --no-default-features
--features wasm --target wasm32-unknown-unknown — green.
- RUSTFLAGS=-D warnings cargo check -p haiai-wasm --tests
--target wasm32-unknown-unknown — green.
- cargo check --workspace (native, all 12 packages) — green.
- cargo test -p haiai --lib — 222 passed.
- cargo test -p jacs-core — 14 tests pass (sign/verify
round-trip including new export path).
- bash scripts/ci/forbidden-deps-wasm.sh haiai-wasm (from rust/)
— clean.
- bash scripts/ci/check_wasm_surface.sh — green.
- npx --yes -p typescript@5 tsc --noEmit -p node-wasm/tsconfig.json
— green.
- WASM_PARITY_SKIP_WASM=1 bash scripts/ci/check_wasm_parity.sh —
correctly fails when wasm.json missing; will validate end-to-end
in CI.
CI-only validations (require chromedriver/Chrome pairing that the
local env doesn't have):
- wasm-pack test --headless --chrome rust/haiai-wasm --test
secret_leak
- wasm-pack test --headless --chrome rust/haiai-wasm --test
parity_snapshot
- bash scripts/ci/check_wasm_parity.sh (runs the wasm-pack test
and diffs)
rust/Cargo.lock updated for the haiai-wasm dev-dep additions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three issues from the second-round review of the prior 001-007 work:
- 009 (critical) blocked CI from validating anything wasm.
- 006 was reported "still open" because the prior secret-leak test
scanned for keypair bytes generated inside the test rather than
the real persisted private-key bytes.
- 010 was a new test-gap issue requiring real lifecycle / persistence
/ publicOnly / clearSecrets / event-stream coverage in the browser
test suite.
Issue 009 (Critical) — wasm-checks CI clones to wrong path + wrong ref.
.github/workflows/test.yml `wasm-checks` job previously cloned JACS
to `${{ github.workspace }}/../JACS` at `$JACS_REF=v0.10.2`. Two
problems:
1. rust/Cargo.toml's `[patch.crates-io]` block points
jacs / jacs-core / jacs-wasm at `../../../wasm/*`, which from
rust/Cargo.toml resolves to `${GITHUB_WORKSPACE}/../../wasm/*`
in CI — not `${GITHUB_WORKSPACE}/../JACS`. Cargo simply could
not find the patched crates.
2. v0.10.2 does not have the APIs the wasm tree consumes
(CoreAgent::export_encrypted_material, verify_with_key,
sign_raw_bytes, verify_raw_bytes_with_key — all WASM-branch
additions).
Fix: the wasm-checks job now clones the WASM branch into
`${{ github.workspace }}/../../wasm` so the path and the API
surface both match what rust/Cargo.toml expects. Other jobs keep
their existing v0.10.2 clone at the legacy `../JACS` path — they
don't depend on the wasm-only APIs.
Issue 006 (High test gap) — secret-leak browser test now exercises
the real persistence path.
rust/haiai-wasm/tests/secret_leak.rs: rewritten end-to-end.
- Builds a real `BrowserAgentHandle` (not a raw `CoreAgent`),
registers it, signs via `signMessageJson`, mocks
`sendSignedEmail` by signing a canonicalized body the same
way the real flow does (`canonical_json` +
`build_auth_header` + `signMessageJson`).
- Saves via the same `export_encrypted` +
`jacs_wasm::local_store::save_encrypted_agent` path the
`BrowserAgent.save(key, password)` TS API uses, then
*decrypts the saved envelope* to recover the actual
private-key bytes — the previous test scanned for an
unrelated probe keypair's bytes, so it could not have caught
a real leak.
- Scans localStorage values for the password literal, the
decrypted private-key bytes in five encodings (standard
base64, url-safe base64, lowercase hex, uppercase hex, raw
utf-8), and PEM private-block markers.
Issue 010 (High test gap) — lifecycle / persistence / public-only /
clearSecrets / event-stream coverage.
rust/haiai-wasm/tests/secret_leak.rs: eight new tests in the same
file:
- ed25519 round-trip: createEphemeral → sign → verify → export
→ import → re-verify.
- pq2025 round-trip: same flow against the post-quantum algo.
- exportEncrypted + import + wrong password rejected with
`InvalidPassword`.
- publicOnly preserves the supplied jacsId, accepts
verifyWithSuppliedKey, rejects local sign with `Locked`.
- clearSecrets locks the underlying provider (proven via
direct CoreAgent::is_unlocked check) and is idempotent.
- save/load round-trip via `localStore.saveEncryptedAgent` →
`loadEncryptedAgent` succeeds with matching jacsId.
- Malformed material + PEM-shaped payload both rejected with
`RefusedPayload`.
- Empty-storage walker survives without panicking.
rust/haiai-wasm/tests/streams_iteration.rs (new): three SSE / WS
parser tests covering multi-event SSE chunks, chunk-boundary
stitching, and a three-frame WS sequence that includes a
heartbeat → pong reply.
Verification:
- RUSTFLAGS="-D warnings" cargo check -p haiai-wasm --tests
--target wasm32-unknown-unknown — green (one ZeroizingVec →
Vec<u8> conversion fix landed during the sweep).
- cargo check -p haiai (native) — green.
The new browser-test execution still requires a working
ChromeDriver/Chrome pairing the local environment doesn't have; CI's
browser-actions/setup-chrome@v1 pairs them correctly. The wasm-pack
test --headless --chrome step (already in wasm-checks) will now
actually run these tests against the right JACS APIs thanks to the
Issue 009 fix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.