feat(sdk,net): full std::net::TcpStream surface mirror#3
Merged
Conversation
Adds `connect(host, port)` to the JS SDK's net module with the exact shape RFC astrid-runtime/rfcs#27 specifies — returns a StreamHandle that flows through the existing recv / tryRecv / send / close surface, same type as the accept path returns. The host fn (`astrid:capsule/net.net-connect-tcp`) hasn't landed yet (tracking astrid-runtime/astrid#745). The body throws SysError.api with the tracking URLs so capsules attempting to use it before the host side ships get a clear, actionable error. Why land the stub now: capsules being built against the final unicity-astrid/wit contract (Unicity / Sphere SDK port, Discord bridges, MQTT, etc.) can import `connect` and write against the final API today. When astrid#745 merges, this commit becomes a one-line swap: replace the `throw SysError.api(...)` with the commented-in `callHost(...)` line above it. No callers change.
Replaces the stub from 5a12d7d with the real impl now that the host side has landed (astrid-runtime/wit#5 + astrid#745 feat/net-connect-tcp). Changes: - packages/astrid-sdk/src/wit-imports.d.ts: declare netConnectTcp in the astrid:capsule/net@0.1.0 ambient module. - packages/astrid-sdk/src/net.ts: connect() now calls hostConnectTcp through the existing callHost wrapper; throws SysError on capability denial, SSRF rejection, DNS failure, or connect timeout. JSDoc lists the three kernel-side checks operators care about (allowlist, SSRF airlock, active-stream cap) so capsule authors don't have to read the host source to understand failure modes. - contracts/: submodule bumped to feat/net-connect-tcp tip (matches the WIT PR head). - packages/astrid-sdk/wit-contracts/astrid-contracts.wit: regenerated by scripts/sync-contracts-wit.sh (no diff vs main beyond canonical updates). Returns the same StreamHandle type as accept() — recv / tryRecv / send / close all Just Work without runtime branching. RFC: astrid-runtime/rfcs#27 WIT: astrid-runtime/wit#5 SDK (rust): unicity-astrid/sdk-rust feat/net-connect-tcp Tracking issue: astrid-runtime/astrid#745
Mirrors the kernel + sdk-rust expansion in unicity-astrid/sdk-rust
feat/net-connect-tcp.
Ambient module additions (astrid:capsule/net@0.1.0):
- ShutdownHow type ('read' | 'write' | 'both')
- 14 new functions: netReadBytes, netWriteBytes, netPeek,
netShutdown, netPeerAddr, netLocalAddr, netSetNodelay, netNodelay,
netSetReadTimeout, netReadTimeout, netSetWriteTimeout,
netWriteTimeout, netSetTtl, netTtl
StreamHandle methods:
- readBytes(maxBytes), writeBytes(data), peek(maxBytes)
- shutdown(how)
- peerAddr(), localAddr()
- setNodelay(b), nodelay()
- setReadTimeout(ms), readTimeout()
- setWriteTimeout(ms), writeTimeout()
- setTtl(n), ttl()
The existing recv / send / close framed surface is unchanged — it's
the right shape for the inbound CLI proxy. Outbound TCP capsules use
the byte-stream pair.
RFC: astrid-runtime/rfcs#27
WIT: astrid-runtime/wit#5
SDK (rust): unicity-astrid/sdk-rust feat/net-connect-tcp
Tracking issue: astrid-runtime/astrid#745
- toHostTimeout helper validates + converts. Rejects 0, NaN, negative,
non-finite — matches std::net::TcpStream::set_{read,write}_timeout
semantics where Duration::ZERO is an error.
- setReadTimeout / setWriteTimeout both go through the helper.
- readBytes / peek docstrings made the EOF-vs-would-block contract
explicit (empty Uint8Array = EOF; would-block surfaces as a
SysError caller can pattern-match).
IPv6 bracket fix from the sdk-rust review doesn't apply here —
JS connect(host, port) takes them as separate args.
ee7c15c to
53a1a7a
Compare
7 tasks
Was pinned at f5039e3 — the FIRST commit on the wit PR branch, which added only the single net-connect-tcp fn. The expanded surface (14 more fns + shutdown-how enum) landed in the subsequent commit 215ba1c that this submodule reference had not been updated to. JS-side bindings in wit-imports.d.ts were already declared for the full surface, so this was a documentation lag rather than a runtime mismatch, but the submodule should reference the canonical main SHA (now 9244c74 post squash-merge).
joshuajbouw
added a commit
to astrid-runtime/astrid
that referenced
this pull request
May 19, 2026
) ## Linked Issue Closes #745 ## Summary Adds outbound TCP to the host ABI: `net-connect-tcp` plus 14 more host fns that give capsules full `std::net::TcpStream` parity. Unblocks every capsule that needs persistent TCP — WebSocket clients (Fulcrum, Nostr), MQTT, Discord/Telegram gateways, postgres / redis. Strictly additive to the existing `net` interface; **TLS is not in scope** (capsules ship their own crate, matching the `std::net` model where TLS lives in user-space). ## Changes - **WIT**: syncs canonical [astrid-runtime/wit#5](astrid-runtime/wit#5) into in-tree `wit/astrid-capsule.wit`. Adds the `shutdown-how` enum + 15 new `net-*` fns (`connect-tcp`, `read-bytes`, `write-bytes`, `peek`, `shutdown`, `peer-addr`, `local-addr`, `set-nodelay` / `nodelay`, `set-read-timeout` / `read-timeout`, `set-write-timeout` / `write-timeout`, `set-ttl` / `ttl`) and brings in `ipc-message.principal` (canonical PR #4) which had drifted from the in-tree copy. - **Manifest**: `CapabilitiesDef.net_connect: Vec<String>` — per-capsule allowlist for outbound TCP destinations. Each entry is `"host:port"` (exact) or `"host:*"` (any port). Empty / missing denies all outbound TCP (fail-closed). Independent of `net_bind`, `http`, `host_process`. - **Kernel** (`engine/wasm/host/net/`): `net_connect_tcp` runs capability check → host string validation → DNS resolve → SSRF airlock (`is_safe_ip`, same gate as `http-request`) → bounded 10s connect. `read_bytes` / `write_bytes` are byte-stream variants honouring per-stream timeouts (empty Vec = EOF unambiguously; timeout fires as `would block` for SDK-side `ErrorKind::WouldBlock` mapping). `peek` uses tokio's native `TcpStream::peek`. `shutdown` uses `socket2::SockRef` for full `Read|Write|Both` direction support. - **Security caps**: per-call read/peek buffer capped at `MAX_BYTES_PER_CALL = 10 MB` (guards against `u32::MAX` OOM). Host string validation (non-empty, ≤255 bytes per RFC 1035, no null bytes) at the host-fn boundary. - **Security gate**: new `CapsuleSecurityGate::check_net_connect` trait method, default-deny. `ManifestSecurityGate` impl matches `host:port` against the manifest allowlist (case-insensitive host, exact-or-`*` port). - **Refactors**: `net.rs` split into `net/{mod,handshake,stream}.rs` and `manifest.rs` split into `manifest/{mod,capabilities,topics}.rs` to stay under the 1000-line CI cap. `pub use` re-exports preserve the public API. ## Test Plan ### Automated - [x] `cargo test --workspace` passes (296 lib tests in astrid-capsule — was 285, + 11 new covering net_connect pattern matching, socket2 shutdown round-trip, validate_host gates) - [x] No new clippy warnings (`cargo clippy --workspace -- -D warnings` clean) - [x] `cargo +nightly fmt --all --check` clean ### Manual - [ ] Tag a SDK release (separate PR per CLAUDE.md) so capsule authors can bump - [ ] Smoke-test a capsule against a public `ws://` endpoint over the new surface (deferred to a follow-up capsule PR) ## Refs - RFC: [astrid-runtime/rfcs#27](astrid-runtime/rfcs#27) - WIT: [astrid-runtime/wit#5](astrid-runtime/wit#5) - SDK (rust): [astrid-runtime/sdk-rust#42](astrid-runtime/sdk-rust#42) - SDK (js): [astrid-runtime/sdk-js#3](astrid-runtime/sdk-js#3) ## Checklist - [x] Linked to an issue - [x] CHANGELOG.md updated under `[Unreleased]`
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.
Summary
JS/TS mirror of unicity-astrid/sdk-rust#42. Adds outbound TCP for capsules —
connect()plus the full set of methods a Rust binary'sstd::net::TcpStreamexposes — on theStreamHandleclass.Changes
Ambient module (
wit-imports.d.ts)Declares the 14 new host fns +
ShutdownHowtype in theastrid:capsule/net@0.1.0module, matching the canonical WIT in unicity-astrid/wit#5.net.tsconnect(host, port)— outbound TCP (already in this branch's tip, now upgraded from a stub).StreamHandlemethods:readBytes(maxBytes)/writeBytes(data)— byte-stream read/write (no length-prefix framing).peek(maxBytes)— non-destructive read.shutdown(how)— half-close ('read' | 'write' | 'both').peerAddr()/localAddr()—ip:portstrings.setNodelay(b)/nodelay()— TCP_NODELAY.setReadTimeout(ms)/readTimeout().setWriteTimeout(ms)/writeTimeout().setTtl(n)/ttl()— IP TTL.Submodule
contracts/bumped tofeat/net-connect-tcptip (matches WIT PR head).Unchanged
The existing framed surface (
recv/tryRecv/send/close+AsyncIterable<Uint8Array>) keeps its semantics — that's the right shape for the inbound CLI proxy. Outbound TCP capsules use the byte-stream pair (readBytes/writeBytes).TLS
Out of scope. JS-side capsules ship their own WebSocket / TLS library over
readBytes/writeBytes, same as native Rust binaries do — std::net::TcpStream doesn't ship TLS, and we're holding that line.Verification
npm run -w packages/astrid-sdk buildclean (tsc -b)Refs