mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-08-25 08:32:14 +00:00
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
684d21a354
|
docs(adr): ADR-339 — a WebAssembly binding for ruv:// context, and its boundary (#925)
* docs(adr): ADR-339 — a WebAssembly binding for ruv:// context, and its boundary Records why a JavaScript consumer cannot reach rvm-context today, and what a binding may and may not carry. The manifest settles the first half: crate-type = ["rlib"] links Rust to Rust, so there is no JS, wasm, or C ABI surface and no feature flag that produces one. Three adjacent facts were verified rather than assumed, each plausible enough to guess wrong about: crates/rvm-wasm is a WebAssembly GUEST runtime for partitions, not a binding; ed25519-dalek is a dev-dependency, so the runtime crypto surface is sha2 and sha3, both wasm32-clean; and a std feature already exists, which wasm-bindgen requires. The decision is a cdylib wrapper crate publishing to npm as @ruvnet/rvm-context, exposing canonical parsing, canonical re-formatting, and the specific UriError variant on rejection -- pure computation over a string. It deliberately does not expose the runtime or resolver paths. Those require an authenticated PartitionId bound at construction and a runtime-owned clock, which exist so a caller cannot supply its own actor or timestamp. Projecting them into JavaScript would mean inventing a JS-side actor -- the forgery the design prevents -- or shipping something that looks like authorization and is not. So a JavaScript consumer gets the naming layer, not the trust layer. That is not a limitation to lift later by adding bindings; it is the separation the namespace is built on, holding at one more boundary. Also records that npm's `rvm` belongs to an unrelated project (Ruff Version Manager, ruffjs/rvm), so any JS distribution here is a new scoped package rather than an update to that one. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5 * docs(adr): revise ADR-339 after reading the real capability API Sizing the binding against origin/main turned up four facts that change the decision, two of which contradict what the release notes imply. Capability handles are not portable. CapabilityHandle is {index, generation} into a LIVE LOCAL CapabilityManager table -- not a bearer token, not signed, not serializable. Two integers handed from a Rust service to a JS host index a different table and mean something else. This is the load-bearing constraint. An allow decision cannot be separated from its witness record: authorize is pub(crate) and AuthorizedRequest construction is private, documented as reachable only after a P1 allow record is appended. A binding that authorizes must carry ContextRuntime and therefore the witness log. There is no entropy requirement. ed25519 lives only in rvm-proof; rvm-witness signs with HMAC-SHA256, deterministic and keyed, and no getrandom or rand exists in the workspace. The hazard is key provisioning -- default_signer() and with_default_key() must not reach JS -- not randomness. No host clock is needed either: LogicalContextClock is a counter from zero. The consequence is that the earlier draft drew the boundary in the wrong place. It excluded authorization entirely; the handle representation shows why that was wrong. The danger was never that JS might mint a capability, because a capability minted in the module grants nothing outside it. The danger is the ILLUSION of authority -- a gateway provisioning its own scopes, rendering a decision, and reporting it as though it said something about a separate Rust-side authority. So the binding widens to four layers (URI, scope, runtime, verification) and the claim sharpens: the module is a faithful deterministic policy SIMULATOR, handles are not portable, a decision binds only to host-provisioned scopes. Correct for shadow mode, not evidence about another authority. Scope containment alone answers the shadow-mode question with no capability, no runtime and no key, so the motivating consumer is unblocked at layer 2. Gates revised accordingly, including that the cross-tenant negative test must place the violating segment LAST -- a containment check inside a short-circuiting loop is green at position 1 while broken for 2..n. Tracks ruvnet/rvm#45. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5 |